
/*-------------------------------------------------------------------------
 *
 * execnodes.h
 *	  执行器状态节点的定义
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/nodes/execnodes.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef EXECNODES_H
#define EXECNODES_H

#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "fmgr.h"
#include "lib/ilist.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
#include "nodes/tidbitmap.h"
#include "partitioning/partdefs.h"
#include "storage/condition_variable.h"
#include "utils/hsearch.h"
#include "utils/queryenvironment.h"
#include "utils/reltrigger.h"
#include "utils/sharedtuplestore.h"
#include "utils/snapshot.h"
#include "utils/sortsupport.h"
#include "utils/tuplesort.h"
#include "utils/tuplestore.h"

struct PlanState;				/* 本文件中的前向引用 */
struct ParallelHashJoinState;
struct ExecRowMark;
struct ExprState;
struct ExprContext;
struct RangeTblEntry;			/* 避免在此处包含 parsenodes.h */
struct ExprEvalStep;			/* 避免在任何地方包含 execExpr.h */
struct CopyMultiInsertBuffer;
struct LogicalTapeSet;


/* ----------------
 *		ExprState 节点
 *
 * ExprState 是表达式评估的顶级节点。
 * 它包含评估表达式的指令（在 ->steps 中）。
 * ----------------
 */
typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
									struct ExprContext *econtext,
									bool *isNull);

/* ExprState->flags 中的位（另见 execExpr.h 中的私有标志位）： */
/* 表达式用于 ExecQual() */
#define EEO_FLAG_IS_QUAL					(1 << 0)

typedef struct ExprState
{
	NodeTag		type;

	uint8		flags;			/* EEO_FLAG_* 位的位掩码，见上文 */

	/*
	 * 用于标量表达式的结果值存储，或用于通过 ExecBuildProjectionInfo() 构建的
	 * 表达式中的各个列结果。
	 */
#define FIELDNO_EXPRSTATE_RESNULL 2
	bool		resnull;
#define FIELDNO_EXPRSTATE_RESVALUE 3
	Datum		resvalue;

	/*
	 * 如果正在投影一个元组结果，则此插槽保存结果；否则为 NULL。
	 */
#define FIELDNO_EXPRSTATE_RESULTSLOT 4
	TupleTableSlot *resultslot;

	/*
	 * 计算表达式返回值的指令。
	 */
	struct ExprEvalStep *steps;

	/*
	 * 实际评估表达式的函数。  根据表达式的复杂性，可以设置为不同的值。
	 */
	ExprStateEvalFunc evalfunc;

	/* 原始表达式树，仅用于调试 */
	Expr	   *expr;

	/* evalfunc 的私有状态 */
	void	   *evalfunc_private;

	/*
	 * XXX：以下字段仅在“编译”期间需要（ExecInitExpr）；
	 * 之后可以丢弃。
	 */

	int			steps_len;		/* 当前步骤数 */
	int			steps_alloc;	/* 步骤数组的分配长度 */

#define FIELDNO_EXPRSTATE_PARENT 11
	struct PlanState *parent;	/* 父 PlanState 节点（如果有） */
	ParamListInfo ext_params;	/* 用于编译 PARAM_EXTERN 节点 */

	Datum	   *innermost_caseval;
	bool	   *innermost_casenull;

	Datum	   *innermost_domainval;
	bool	   *innermost_domainnull;
} ExprState;


/* ----------------
 *	  IndexInfo 信息
 *
 *		此结构包含构造特定索引的新索引条目所需的信息。
 *		用于索引构建和索引条目的零售创建。
 *
 *		NumIndexAttrs		此索引中的列总数
 *		NumIndexKeyAttrs	索引中的关键列数
 *		IndexAttrNumbers	用作键的基础关系属性编号
 *							（零表示表达式）。它还包含有关包含列的信息。
 *		Expressions			表达式条目的表达式树，如果没有则为 NIL
 *		ExpressionsState	表达式的执行状态，如果没有则为 NIL
 *		Predicate			部分索引谓词，如果没有则为 NIL
 *		PredicateState		谓词的执行状态，如果没有则为 NIL
 *		ExclusionOps		每列的排除操作符，如果没有则为 NULL
 *		ExclusionProcs		排除操作符的基础函数 OID
 *		ExclusionStrats		排除操作符的操作类策略编号
 *		UniqueOps			这些类似于 Exclusion*，但用于唯一索引
 *		UniqueProcs
 *		UniqueStrats
 *		Unique				这是唯一索引吗？
 *		OpclassOptions		操作类特定选项，如果没有则为 NULL
 *		ReadyForInserts		是否支持插入？
 *		CheckedUnchanged	索引未更改状态已确定吗？
 *		IndexUnchanged		aminsert 提示，缓存用于零售插入
 *		Concurrent			我们是否在进行并发索引构建？
 *		BrokenHotChain		我们是否检测到任何损坏的 HOT 链？
 *		ParallelWorkers		请求的工作线程数量（不包括领导者）
 *		Am					索引 AM 的 Oid
 *		AmCache				索引 AM 的私有缓存区域
 *		Context				持有此 IndexInfo 的内存上下文
 *
 * ii_Concurrent、ii_BrokenHotChain 和 ii_ParallelWorkers 仅在索引构建期间使用；
 * 否则，按照惯例初始化为零。
 * ----------------
 */
typedef struct IndexInfo
{
	NodeTag		type;
	int			ii_NumIndexAttrs;	/* 索引中的总列数 */
	int			ii_NumIndexKeyAttrs;	/* 索引中的键列数 */
	AttrNumber	ii_IndexAttrNumbers[INDEX_MAX_KEYS];
	List	   *ii_Expressions; /* 表达式列表 */
	List	   *ii_ExpressionsState;	/* ExprState 列表 */
	List	   *ii_Predicate;	/* 表达式列表 */
	ExprState  *ii_PredicateState;
	Oid		   *ii_ExclusionOps;	/* 每列一个条目的数组 */
	Oid		   *ii_ExclusionProcs;	/* 每列一个条目的数组 */
	uint16	   *ii_ExclusionStrats; /* 每列一个条目的数组 */
	Oid		   *ii_UniqueOps;	/* 每列一个条目的数组 */
	Oid		   *ii_UniqueProcs; /* 每列一个条目的数组 */
	uint16	   *ii_UniqueStrats;	/* 每列一个条目的数组 */
	Datum	   *ii_OpclassOptions;	/* 每列一个条目的数组 */
	bool		ii_Unique;
	bool		ii_NullsNotDistinct;
	bool		ii_ReadyForInserts;
	bool		ii_CheckedUnchanged;
	bool		ii_IndexUnchanged;
	bool		ii_Concurrent;
	bool		ii_BrokenHotChain;
	int			ii_ParallelWorkers;
	Oid			ii_Am;
	void	   *ii_AmCache;
	MemoryContext ii_Context;
} IndexInfo;

/* ----------------
 *	  ExprContext_CB
 *
 *		定义在ExprContext关闭时调用的回调函数列表。
 * ----------------
 */
typedef void (*ExprContextCallbackFunction) (Datum arg);

typedef struct ExprContext_CB
{
	struct ExprContext_CB *next;
	ExprContextCallbackFunction function;
	Datum		arg;
} ExprContext_CB;

/* ----------------
 *	  ExprContext
 *
 *		此类保存"当前上下文"信息，
 *		用于评估表达式以执行元组资格检查
 *		和元组投影。例如，如果一个表达式引用了
 *		当前内元组中的一个属性，那么我们需要知道
 *		当前内元组是什么，因此我们查看表达式
 *		上下文。
 *
 *	有两个与ExprContext相关的内存上下文：
 *	* ecxt_per_query_memory是查询生命周期上下文，通常与
 *	  ExprContext节点本身分配在同一上下文中。此上下文
 *	  可用于存储函数调用缓存信息等目的。
 *	* ecxt_per_tuple_memory是用于表达式结果的短期上下文。
 *	  正如名称所示，它通常在评估该元组的表达式之前
 *	  每个元组重置一次。每个ExprContext通常都有其
 *	  自己的每个元组内存上下文。
 *
 *	在调用ExecEvalExpr()之前，CurrentMemoryContext应设置为
 *	ecxt_per_tuple_memory---参见ExecEvalExprSwitchContext()。
 * ----------------
 */
typedef struct ExprContext
{
	NodeTag		type;

	/* 表达式中Var节点可能引用的元组 */
#define FIELDNO_EXPRCONTEXT_SCANTUPLE 1
	TupleTableSlot *ecxt_scantuple;
#define FIELDNO_EXPRCONTEXT_INNERTUPLE 2
	TupleTableSlot *ecxt_innertuple;
#define FIELDNO_EXPRCONTEXT_OUTERTUPLE 3
	TupleTableSlot *ecxt_outertuple;

	/* 表达式评估的内存上下文---见上面的说明 */
	MemoryContext ecxt_per_query_memory;
	MemoryContext ecxt_per_tuple_memory;

	/* 用于替代表达式中Param节点的值 */
	ParamExecData *ecxt_param_exec_vals;	/* 用于PARAM_EXEC参数 */
	ParamListInfo ecxt_param_list_info; /* 用于其他参数类型 */

	/*
	 * 在Agg节点的表达式中，或在WindowAgg节点内的
	 * WindowFunc节点中，用于替代Aggref节点的值。
	 */
#define FIELDNO_EXPRCONTEXT_AGGVALUES 8
	Datum	   *ecxt_aggvalues; /* aggs/windowfuncs的预计算值 */
#define FIELDNO_EXPRCONTEXT_AGGNULLS 9
	bool	   *ecxt_aggnulls;	/* aggs/windowfuncs的null标志 */

	/* 用于替代表达式中CaseTestExpr节点的值 */
#define FIELDNO_EXPRCONTEXT_CASEDATUM 10
	Datum		caseValue_datum;
#define FIELDNO_EXPRCONTEXT_CASENULL 11
	bool		caseValue_isNull;

	/* 用于替代表达式中CoerceToDomainValue节点的值 */
#define FIELDNO_EXPRCONTEXT_DOMAINDATUM 12
	Datum		domainValue_datum;
#define FIELDNO_EXPRCONTEXT_DOMAINNULL 13
	bool		domainValue_isNull;

	/* 链接到包含的EState（如果是独立的ExprContext则为NULL） */
	struct EState *ecxt_estate;

	/* 当ExprContext关闭或重新扫描时调用的回调函数 */
	ExprContext_CB *ecxt_callbacks;
} ExprContext;

/*
 * 在评估可能返回集合的函数时使用的返回状态。
 */
typedef enum
{
	ExprSingleResult,			/* 表达式不返回集合 */
	ExprMultipleResult,			/* 该结果是集合的一个元素 */
	ExprEndResult				/* 集合中没有更多元素 */
} ExprDoneCond;

/*
 * 返回集合的函数的返回模式。注意，值必须选择
 * 为单独的位，以便可以形成位掩码以指示支持的
 * 模式。SFRM_Materialize_Random和SFRM_Materialize_Preferred是
 * 关于SFRM_Materialize模式的辅助标志，而不是单独的模式。
 */
typedef enum
{
	SFRM_ValuePerCall = 0x01,	/* 每次调用返回一个值 */
	SFRM_Materialize = 0x02,	/* 在Tuplestore中实例化的结果集 */
	SFRM_Materialize_Random = 0x04, /* Tuplestore需要随机访问 */
	SFRM_Materialize_Preferred = 0x08	/* 调用者更喜欢Tuplestore */
} SetFunctionReturnMode;

/*
 * 当调用可能返回集合（多行）的函数时，
 * 这种类型的节点作为fcinfo->resultinfo传递，
 * 以允许返回状态被传递回来。如果没有提供这样的
 * resultinfo，返回集合的函数应引发错误。
 */
typedef struct ReturnSetInfo
{
	NodeTag		type;
	/* 调用者设置的值： */
	ExprContext *econtext;		/* 函数被调用的上下文 */
	TupleDesc	expectedDesc;	/* 调用者期望的元组描述符 */
	int			allowedModes;	/* 位掩码：调用者可以处理的返回模式 */
	/* 从函数返回的结果状态（但由调用者预初始化）： */
	SetFunctionReturnMode returnMode;	/* 实际返回模式 */
	ExprDoneCond isDone;		/* ValuePerCall模式的状态 */
	/* 在Materialize返回模式下由函数填充的字段： */
	Tuplestorestate *setResult; /* 保存完整的返回元组集 */
	TupleDesc	setDesc;		/* 返回元组的实际描述符 */
} ReturnSetInfo;

/* ----------------
 *		ProjectionInfo节点信息
 *
 *		这是执行投影所需的所有信息---
 *		也就是说，通过评估targetlist表达式形成新的元组。
 *		需要进行投影的节点创建其中一个。
 *
 *		目标元组插槽保存在ProjectionInfo->pi_state.resultslot中。
 *		ExecProject()评估tlist，形成一个元组，并将其
 *		存储在给定的插槽中。请注意，结果将是一个“虚拟”元组
 *		除非随后调用ExecMaterializeSlot()以强制将其
 *		转换为物理元组。插槽必须具有与tlist输出匹配的tupledesc！
 * ----------------
 */
typedef struct ProjectionInfo
{
	NodeTag		type;
	/* 评估投影的指令 */
	ExprState	pi_state;
	/* 评估表达式的表达式上下文 */
	ExprContext *pi_exprContext;
} ProjectionInfo;

/* ----------------
 *	  JunkFilter
 *
 *	  此类用于存储有关垃圾属性的信息。
 *	  垃圾属性是元组中的属性，仅在执行器中存储中间信息时需要，
 *	  并不属于发出的元组。例如，当我们执行UPDATE查询时，
 *	  规划程序将“垃圾”条目添加到targetlist中，以便返回给ExecutePlan()的元组
 *	  包含一个额外的属性：要更新的元组的ctid。这是进行更新所需的，
 *	  但我们不希望ctid成为存储的新元组的一部分！所以，我们
 *	  应用“垃圾过滤器”来移除垃圾属性并形成真实的输出元组。
 *	  垃圾过滤器代码还提供了从输入元组中提取垃圾属性值的例程。
 *
 *	  targetList:		原始目标列表（包括垃圾属性）。
 *	  cleanTupType:		“干净”元组的元组描述符（去除了垃圾属性）。
 *	  cleanMap:			一个映射，表示“原始”元组的非垃圾
 *						属性编号与“干净”元组的属性编号之间的对应关系。
 *	  resultSlot:		用于存放清理后元组的插槽。
 * ----------------
 */
typedef struct JunkFilter
{
	NodeTag		type;
	List	   *jf_targetList;
	TupleDesc	jf_cleanTupType;
	AttrNumber *jf_cleanMap;
	TupleTableSlot *jf_resultSlot;
} JunkFilter;

/*
 * OnConflictSetState
 *
 * ON CONFLICT DO UPDATE操作的执行器状态。
 */
typedef struct OnConflictSetState
{
	NodeTag		type;

	TupleTableSlot *oc_Existing;	/* 存储现有目标元组的插槽 */
	TupleTableSlot *oc_ProjSlot;	/* CONFLICT ... SET ... 投影目标 */
	ProjectionInfo *oc_ProjInfo;	/* 用于ON CONFLICT DO UPDATE SET */
	ExprState  *oc_WhereClause; /* WHERE子句的状态 */
} OnConflictSetState;

/* ----------------
 *	 MergeActionState信息
 *
 *	合并操作的执行器状态。
 * ----------------
 */
typedef struct MergeActionState
{
	NodeTag		type;

	MergeAction *mas_action;	/* 关联的MergeAction节点 */
	ProjectionInfo *mas_proj;	/* 此关系的操作目标列表的投影 */
	ExprState  *mas_whenqual;	/* WHEN [NOT] MATCHED AND条件 */
} MergeActionState;

/*
 * ResultRelInfo
 *
 * 每当我们更新现有关系时，我们必须更新该关系上的索引，
 * 并可能还要触发触发器。ResultRelInfo保存有关结果关系的所有
 * 信息，包括索引。
 *
 * 通常，ResultRelInfo引用的表在查询的范围表中；
 * 此时，ri_RangeTableIndex是RT索引，ri_RelationDesc是
 * 相关es_relations[]条目的副本。然而，在某些情况下，
 * 我们为不在范围表中的关系创建ResultRelInfo，
 * 例如，分区表中的元组路由目标，以及在目标表以外的
 * 表中触发触发器时（参见ExecGetTriggerResultRel）。
 * 在这些情况下，ri_RangeTableIndex为0，
 * ri_RelationDesc是需要单独关闭的单独打开的relcache指针。
 */
typedef struct ResultRelInfo
{
	NodeTag		type;

	/* 结果关系的范围表索引，若不在范围表中则为0 */
	Index		ri_RangeTableIndex;

	/* 结果关系的关系描述符 */
	Relation	ri_RelationDesc;

	/* 结果关系上存在的索引数量 */
	int			ri_NumIndices;

	/* 用于索引的关系描述符数组 */
	RelationPtr ri_IndexRelationDescs;

	/* 用于索引的键/属性信息数组 */
	IndexInfo **ri_IndexRelationInfo;

	/*
	 * 对于UPDATE/DELETE结果关系，源计划输出元组中行
	 * 身份垃圾属性的属性编号
	 */
	AttrNumber	ri_RowIdAttNo;

	/* 在INSERT/UPDATE中生成新元组的投影 */
	ProjectionInfo *ri_projectNew;
	/* 用于保存该元组的插槽 */
	TupleTableSlot *ri_newTupleSlot;
	/* 用于保存正在更新的旧元组的插槽 */
	TupleTableSlot *ri_oldTupleSlot;
	/* 上述投影和插槽是否已初始化？ */
	bool		ri_projectNewInfoValid;

	/* 更新在读取oldtup之前会执行LockTuple();参见README.tuplock */
	bool		ri_needLockTagTuple;

	/* 触发器触发的条件，如果有的话 */
	TriggerDesc *ri_TrigDesc;

	/* 触发函数的缓存查找信息 */
	FmgrInfo   *ri_TrigFunctions;

	/* 触发器WHEN表达式状态的数组 */
	ExprState **ri_TrigWhenExprs;

	/* 触发器的可选运行时测量 */
	Instrumentation *ri_TrigInstrument;

	/* 按需创建的触发器/返回处理的插槽 */
	TupleTableSlot *ri_ReturningSlot;	/* 用于触发器输出元组 */
	TupleTableSlot *ri_TrigOldSlot; /* 用于触发器的旧元组 */
	TupleTableSlot *ri_TrigNewSlot; /* 用于触发器的新元组 */

	/* FDW回调函数，如果是外部表 */
	struct FdwRoutine *ri_FdwRoutine;

	/* 可用于保存FDW的私有状态 */
	void	   *ri_FdwState;

	/* 直接修改外部表时为真 */
	bool		ri_usesFdwDirectModify;

	/* 批量插入相关内容 */
	int			ri_NumSlots;	/* 数组中的插槽数量 */
	int			ri_NumSlotsInitialized; /* 已初始化插槽的数量 */
	int			ri_BatchSize;	/* 单次批处理插入的最大插槽数量 */
	TupleTableSlot **ri_Slots;	/* 用于批量插入的输入元组 */
	TupleTableSlot **ri_PlanSlots;

	/* 需要检查的WithCheckOption列表 */
	List	   *ri_WithCheckOptions;

	/* WithCheckOption表达式状态的列表 */
	List	   *ri_WithCheckOptionExprs;

	/* 约束检查表达式状态的数组 */
	ExprState **ri_ConstraintExprs;

	/* 存储生成列表达式状态的数组 */
	ExprState **ri_GeneratedExprs;

	/* 我们需要计算的存储生成列的数量 */
	int			ri_NumGeneratedNeeded;

	/* RETURNING表达式的列表 */
	List	   *ri_returningList;

	/* 用于计算RETURNING列表 */
	ProjectionInfo *ri_projectReturning;

	/* 用于检查冲突的仲裁索引列表 */
	List	   *ri_onConflictArbiterIndexes;

	/* ON CONFLICT评估状态 */
	OnConflictSetState *ri_onConflict;

	/* 对于MERGE，MergeActionState的列表 */
	List	   *ri_matchedMergeAction;
	List	   *ri_notMatchedMergeAction;

	/* 分区检查表达式状态（如果尚未设置则为NULL） */
	ExprState  *ri_PartitionCheckExpr;

	/*
	 * 元组路由目标关系所需的信息
	 *
	 * RootResultRelInfo提供查询中提到的目标关系，如果
	 * 这是一个分区表。如果查询中提到的目标关系
	 * 是一个继承表，则不会设置此值; 当不需要元组路由时
	 * 也不会设置。
	 *
	 * RootToPartitionMap和PartitionTupleSlot，由
	 * ExecInitRoutingInfo初始化，如果分区的元组
	 * 格式与根表不同，则为非NULL。
	 */
	struct ResultRelInfo *ri_RootResultRelInfo;
	TupleConversionMap *ri_RootToPartitionMap;
	TupleTableSlot *ri_PartitionTupleSlot;

	/*
	 * 将子结果关系元组转换为实际在查询中提到的表
	 * （称为“根”）格式的映射。仅在
	 * 需要时计算。NULL映射值表明不需要转换，因此我们
	 * 必须有一个单独的标志来显示映射是否已被计算。
	 */
	TupleConversionMap *ri_ChildToRootMap;
	bool		ri_ChildToRootMapValid;

	/* 用于copyfrom.c在执行多插入时使用 */
	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;

	/*
	 * 用于当一个叶分区涉及其祖先的跨分区更新时；参见 ExecCrossPartitionUpdateForeignKey()。
	 */
	List	   *ri_ancestorResultRels;
} ResultRelInfo;

/*
 * 为了避免在后向分支中 ResultRelInfo 大小的 ABI 破坏性变化，我们为每个已经计算了 extraUpdatedCols 的结果关系创建一个这样的对象，并将其存储在 EState.es_resultrelinfo_extra 中。
 */
typedef struct ResultRelInfoExtra
{
	ResultRelInfo *rinfo;		/* 拥有的 ResultRelInfo */

	/* 对于 INSERT/UPDATE，待计算的生成列的 attnums */
	Bitmapset  *ri_extraUpdatedCols;
} ResultRelInfoExtra;

/* ----------------
 *	  AsyncRequest
 *
 * 异步元组请求的状态。
 * ----------------
 */
typedef struct AsyncRequest
{
	struct PlanState *requestor;	/* 想要元组的节点 */
	struct PlanState *requestee;	/* 被请求元组的节点 */
	int			request_index;	/* 请求者的临时空间 */
	bool		callback_pending;	/* 需要回调 */
	bool		request_complete;	/* 请求完成，结果有效 */
	TupleTableSlot *result;		/* 结果（如果没有更多元组，则为 NULL 或空插槽） */
} AsyncRequest;

/* ----------------
 *	  EState 信息
 *
 * 执行器调用的工作状态
 * ----------------
 */
typedef struct EState
{
	NodeTag		type;

	/* 所有查询类型的基本状态： */
	ScanDirection es_direction; /* 当前扫描方向 */
	Snapshot	es_snapshot;	/* 要使用的时间条件 */
	Snapshot	es_crosscheck_snapshot; /* RI 的交叉检查时间条件 */
	List	   *es_range_table; /* RangeTblEntry 的列表 */
	Index		es_range_table_size;	/* 范围表数组的大小 */
	Relation   *es_relations;	/* 每个范围表项的 Relation 指针数组，如果尚未打开，则为 NULL */
	struct ExecRowMark **es_rowmarks;	/* 每个范围表项的 ExecRowMarks 数组，如果没有则为 NULL */
	PlannedStmt *es_plannedstmt;	/* 链接到计划树顶部 */
	const char *es_sourceText;	/* 来自 QueryDesc 的源文本 */

	JunkFilter *es_junkFilter;	/* 顶级垃圾过滤器（如有） */

	/* 如果查询可以插入/删除元组，标记它们的命令 ID */
	CommandId	es_output_cid;

	/* 关于插入/更新/删除查询的目标表的信息： */
	ResultRelInfo **es_result_relations;	/* 每个范围表项的 ResultRelInfo 指针数组，如果不是目标表则为 NULL */
	List	   *es_opened_result_relations; /* es_result_relations 中的非 NULL 条目的列表，顺序不具体 */

	PartitionDirectory es_partition_directory;	/* 用于 PartitionDesc 查找 */

	/*
	 * 以下列表包含由元组路由代码为未在 es_result_relations 数组中找到的分区创建的 ResultRelInfos。
	 */
	List	   *es_tuple_routing_result_relations;

	/* 用于触发器触发的内容： */
	List	   *es_trig_target_relations;	/* 仅触发器的 ResultRelInfos */

	/* 参数信息: */
	ParamListInfo es_param_list_info;	/* 外部参数的值 */
	ParamExecData *es_param_exec_vals;	/* 内部参数的值 */

	QueryEnvironment *es_queryEnv;	/* 查询环境 */

	/* 其他工作状态: */
	MemoryContext es_query_cxt; /* EState 存在的每个查询上下文 */

	List	   *es_tupleTable;	/* TupleTableSlots 列表 */

	uint64		es_processed;	/* 处理的元组数量 */

	int			es_top_eflags;	/* 传递给 ExecutorStart 的 eflags */
	int			es_instrument;	/* InstrumentOption 标志的或运算 */
	bool		es_finished;	/* 当 ExecutorFinish 完成时为 true */

	List	   *es_exprcontexts;	/* EState 内的 ExprContexts 列表 */

	List	   *es_subplanstates;	/* 子计划的 PlanState 列表 */

	List	   *es_auxmodifytables; /* 次要 ModifyTableStates 列表 */

	/*
	 * 此 ExprContext 用于每个输出元组的操作，如约束检查
	 * 和索引值计算。它将在每个输出元组时重置。
	 * 注意，只有在需要时才会创建它。
	 */
	ExprContext *es_per_tuple_exprcontext;

	/*
	 * 如果不为 NULL，这是一个 EPQState 的 EState。这个字段
	 * 在 EState 中存在是为了让感知 EvalPlanQual 的执行节点
	 * 检测到它们需要执行与 EPQ 相关的工作，并提供
	 * 进行该工作的必要信息。
	 */
	struct EPQState *es_epq_active;

	bool		es_use_parallel_mode;	/* 我们可以使用并行工作者吗？ */

	/* 用于并行执行的每个查询共享内存区。 */
	struct dsa_area *es_query_dsa;

	/*
	 * JIT 信息。es_jit_flags 指示是否应执行 JIT
	 * 以及使用哪些选项。es_jit 在执行 JIT 时按需创建。
	 *
	 * es_jit_worker_instr 是所有工作者按需分配的
	 * 组合仪器。领导者的仪器保持分开，并由 ExplainPrintJITSummary() 按需组合。
	 */
	int			es_jit_flags;
	struct JitContext *es_jit;
	struct JitInstrumentation *es_jit_worker_instr;

	/*
	 * 要执行批量插入的外部表的 ResultRelInfos 列表，
	 * 以及拥有的 ModifyTableStates，按相同顺序存储。
	 */
	List	   *es_insert_pending_result_relations;
	List	   *es_insert_pending_modifytables;

	/* ResultRelInfoExtra 结构的列表（见上文） */
	List	   *es_resultrelinfo_extra;
} EState;


/*
 * ExecRowMark -
 *	   FOR [KEY] UPDATE/SHARE 子句的运行时表示
 *
 * 在执行 UPDATE/DELETE/MERGE/SELECT FOR [KEY] UPDATE/SHARE 时，
 * 我们将为查询中的每个非目标关系（除继承父 RTE 外，运行时可以忽略）
 * 有一个 ExecRowMark。虚拟关系，如 FROM 中的子查询，将有一个
 * relation == NULL 的 ExecRowMark。有关大多数字段的详细信息，
 * 请参见 PlanRowMark。除了直接来自 PlanRowMark 的字段外，
 * 我们还存储一个活动标志（用于表示继承树的非活动子节点），
 * curCtid，用于 WHERE CURRENT OF 代码，以及 ermExtra，
 * 计划节点可以使用它（例如，对于外部表，FDW 可以使用
 * ermExtra 保存信息）。
 *
 * EState->es_rowmarks 是这些结构的数组，由 RT 索引索引，
 * 对于不相关的 RT 索引，设置为 NULL。如果没有 rowmarks，
 * es_rowmarks 本身为 NULL。
 */
typedef struct ExecRowMark
{
	Relation	relation;		/* 已打开并适当锁定的关系 */
	Oid			relid;			/* 其 OID（如果是子查询则为 InvalidOid） */
	Index		rti;			/* 其范围表索引 */
	Index		prti;			/* 如果是子节点，则为父范围表索引 */
	Index		rowmarkId;		/* 对于 resjunk 列的唯一标识符 */
	RowMarkType markType;		/* 见 nodes/plannodes.h 中的枚举 */
	LockClauseStrength strength;	/* LockingClause 的强度，或 LCS_NONE */
	LockWaitPolicy waitPolicy;	/* NOWAIT 和 SKIP LOCKED */
	bool		ermActive;		/* 此标记对于当前元组相关吗？ */
	ItemPointerData curCtid;	/* 当前锁定元组的ctid，如果有的话 */
	void	   *ermExtra;		/* 供关系源节点使用 */
} ExecRowMark;

/*
 * ExecAuxRowMark -
 *	   FOR [KEY] UPDATE/SHARE子句的额外运行时表示
 *
 * 每个LockRows和ModifyTable节点都保留了一份需要处理的行标记列表。
 * 除了指向es_rowmarks中相关条目的指针之外，这个结构还携带与行标记相关的
 * resjunk列的列号（有关PlanRowMark的更多详细信息，请参见注释）。
 */
typedef struct ExecAuxRowMark
{
	ExecRowMark *rowmark;		/* es_rowmarks中的相关条目 */
	AttrNumber	ctidAttNo;		/* ctid垃圾属性的resno（如果有） */
	AttrNumber	toidAttNo;		/* tableoid垃圾属性的resno（如果有） */
	AttrNumber	wholeAttNo;		/* whole-row垃圾属性的resno（如果有） */
} ExecAuxRowMark;


/* ----------------------------------------------------------------
 *				 元组哈希表
 *
 * 完全在内存中的元组哈希表用于多个目的。
 *
 * 注意：tab_hash_funcs是为存储在表中的键数据类型而设，
 * 而tab_eq_funcs是针对这些类型的非交叉类型相等操作符。
 * 通常情况下，这些是唯一使用的函数，但FindTupleHashEntry()
 * 支持通过交叉数据类型哈希搜索哈希表。为此，调用者必须提供
 * LHS数据类型的哈希函数以及要使用的交叉类型相等操作符。
 * 在进行此类搜索时，in_hash_funcs和cur_eq_func设置为指向
 * 调用者的函数数组。在LookupTupleHashEntry()期间，它们分别指向
 * tab_hash_funcs和tab_eq_func。
 * ----------------------------------------------------------------
 */
typedef struct TupleHashEntryData *TupleHashEntry;
typedef struct TupleHashTableData *TupleHashTable;

typedef struct TupleHashEntryData
{
	MinimalTuple firstTuple;	/* 此组中第一个元组的副本 */
	void	   *additional;		/* 用户数据 */
	uint32		status;			/* 哈希状态 */
	uint32		hash;			/* 哈希值（缓存） */
} TupleHashEntryData;

/* 定义生成元组哈希表接口所需的参数 */
#define SH_PREFIX tuplehash
#define SH_ELEMENT_TYPE TupleHashEntryData
#define SH_KEY_TYPE MinimalTuple
#define SH_SCOPE extern
#define SH_DECLARE
#include "lib/simplehash.h"

typedef struct TupleHashTableData
{
	tuplehash_hash *hashtab;	/* 底层哈希表 */
	int			numCols;		/* 查找键中的列数 */
	AttrNumber *keyColIdx;		/* 键列的属性编号 */
	FmgrInfo   *tab_hash_funcs; /* 表数据类型的哈希函数 */
	ExprState  *tab_eq_func;	/* 表数据类型的比较器 */
	Oid		   *tab_collations; /* 哈希和比较的排序规则 */
	MemoryContext tablecxt;		/* 包含表的内存上下文 */
	MemoryContext tempcxt;		/* 函数评估的上下文 */
	Size		entrysize;		/* 每个哈希条目的实际大小 */
	TupleTableSlot *tableslot;	/* 引用表条目的槽 */
	/* 以下字段在每次表搜索时临时设置： */
	TupleTableSlot *inputslot;	/* 当前输入元组的槽 */
	FmgrInfo   *in_hash_funcs;	/* 输入数据类型的哈希函数 */
	ExprState  *cur_eq_func;	/* 输入与表的比较器 */
	uint32		hash_iv;		/* 哈希函数IV */
	ExprContext *exprcontext;	/* 表达式上下文 */
}			TupleHashTableData;

typedef tuplehash_iterator TupleHashIterator;

/*
 * 使用InitTupleHashIterator/TermTupleHashIterator进行读/写扫描。
 * 如果表可以被冻结，请使用ResetTupleHashIterator（在此情况下
 * 不需要显式扫描终止）。
 */
#define InitTupleHashIterator(htable, iter) \
	tuplehash_start_iterate(htable->hashtab, iter)
#define TermTupleHashIterator(iter) \
	((void) 0)
#define ResetTupleHashIterator(htable, iter) \
	InitTupleHashIterator(htable, iter)
#define ScanTupleHashTable(htable, iter) \
	tuplehash_iterate(htable->hashtab, iter)


/* ----------------------------------------------------------------
 *				 表达式状态节点
 *
 * 以前，每个计划表达式树中的节点都有一个单独的执行器表达式状态节点。
 * 现在不是这样了；对于常见的表达式节点类型，所有执行信息都嵌入
 * 到单个ExprState节点的步骤中。但我们仍然有一些执行器状态
 * 节点类型用于选定的表达式节点类型，主要是那些需与执行状态树
 * 的其他部分共享信息的类型。
 * ----------------------------------------------------------------
 */

/* ----------------
 *		WindowFuncExprState节点
 * ----------------
 */
typedef struct WindowFuncExprState
{
	NodeTag		type;
	WindowFunc *wfunc;			/* 表达式计划节点 */
	List	   *args;			/* ExprStates for argument expressions */
	ExprState  *aggfilter;		/* FILTER 表达式 */
	int			wfuncno;		/* wfunc 在其计划节点中的 ID 号 */
} WindowFuncExprState;


/* ----------------
 *		SetExprState 节点
 *
 * 用于评估潜在的集合返回表达式的状态（如 FuncExpr
 * 或 OpExpr）。在某些情况下，例如在 ROWS FROM(...)
 * 中的某些表达式，该表达式可能不是 SRF，但仍使用与 SRF 相同的机制；
 * 它将被视为返回单行的 SRF。
 * ----------------
 */
typedef struct SetExprState
{
	NodeTag		type;
	Expr	   *expr;			/* 表达式计划节点 */
	List	   *args;			/* ExprStates for argument expressions */

	/*
	 * 在 ROWS FROM 中，可以内联函数，从而去除通常在其内部的 FuncExpr。
	 * 在这种情况下，这是编译后的表达式（不能返回集合），
	 * 将使用常规 ExecEvalExpr() 进行评估。
	 */
	ExprState  *elidedFuncState;

	/*
	 * 目标函数的函数管理器查找信息。如果 func.fn_oid
	 * 是 InvalidOid，则我们尚未初始化它（也没有初始化任何后续
	 * 字段，除了 funcReturnsSet）。
	 */
	FmgrInfo	func;

	/*
	 * 对于返回元组存储的集合返回函数（SRF），我们在此保留
	 * 元组存储，并一次性分发结果行。槽位保存当前返回的行。
	 */
	Tuplestorestate *funcResultStore;
	TupleTableSlot *funcResultSlot;

	/*
	 * 在某些情况下，我们需要计算函数输出的元组描述符。
	 * 如果是这样，它会存储在这里。
	 */
	TupleDesc	funcResultDesc;
	bool		funcReturnsTuple;	/* 当 funcResultDesc 不是 NULL 时有效 */

	/*
	 * 记住函数是否声明为返回集合。通过 ExecInitExpr 设置，
	 * 即使在 FmgrInfo 设置之前也有效。
	 */
	bool		funcReturnsSet;

	/*
	 * 当我们正在评估使用按调用值模式的集合返回函数
	 * 并且正处于调用系列中时，setArgsValid 为真；
	 * 我们希望再次将相同的参数值传递给函数
	 * （一次又一次，直到它返回 ExprEndResult）。
	 * 这表明 fcinfo_data 已经包含有效的参数数据。
	 */
	bool		setArgsValid;

	/*
	 * 标志以记住我们是否为此 SetExprState 注册了一个关闭回调。
	 * 仅在 funcResultStore 或 setArgsValid 至少被设置一次的情况下进行注册
	 * （因为回调的作用只是释放元组存储或清除 setArgsValid）。
	 */
	bool		shutdown_reg;	/* 已注册关闭回调 */

	/*
	 * 函数的调用参数结构。如果 func.fn_oid 有效，则已
	 * 初始化（由 InitFunctionCallInfoData）。它还在调用之间保存
	 * 参数值，当 setArgsValid 为真时。
	 */
	FunctionCallInfo fcinfo;
} SetExprState;

/* ----------------
 *		SubPlanState 节点
 * ----------------
 */
typedef struct SubPlanState
{
	NodeTag		type;
	SubPlan    *subplan;		/* 表达式计划节点 */
	struct PlanState *planstate;	/* 子选择计划的状态树 */
	struct PlanState *parent;	/* 父计划节点的状态树 */
	ExprState  *testexpr;		/* 合并表达式的状态 */
	List	   *args;			/* 参数表达式的状态 */
	HeapTuple	curTuple;		/* 子计划中最近的元组的副本 */
	Datum		curArray;		/* ARRAY() 子计划中最近的数组 */
	/* 在对子选择输出进行哈希时使用： */
	TupleDesc	descRight;		/* 投影后的子选择描述 */
	ProjectionInfo *projLeft;	/* 投影左侧表达式 */
	ProjectionInfo *projRight;	/* 投影子选择输出 */
	TupleHashTable hashtable;	/* 无空值子选择行的哈希表 */
	TupleHashTable hashnulls;	/* 包含空值的行的哈希表 */
	bool		havehashrows;	/* 如果哈希表不为空则为真 */
	bool		havenullrows;	/* 如果 hashnulls 不为空则为真 */
	MemoryContext hashtablecxt; /* 包含哈希表的内存上下文 */
	MemoryContext hashtempcxt;	/* 临时内存上下文用于哈希表 */
	ExprContext *innerecontext; /* 计算内部元组的上下文 */
	int			numCols;		/* 被哈希的列数 */
	/* 每个剩余字段是长度为 numCols 的数组： */
	AttrNumber *keyColIdx;		/* 哈希表的控制数据 */
	Oid		   *tab_eq_funcoids;	/* 表的相等函数 oid
									 * 数据类型(s) */
	Oid		   *tab_collations; /* 哈希和比较的排序规则 */
	FmgrInfo   *tab_hash_funcs; /* 表数据类型的哈希函数 */
	FmgrInfo   *tab_eq_funcs;	/* 表数据类型(s) 的相等函数 */
	FmgrInfo   *lhs_hash_funcs; /* 左侧数据类型(s) 的哈希函数 */
	FmgrInfo   *cur_eq_funcs;	/* 左侧 vs. 表的相等函数 */
	ExprState  *cur_eq_comp;	/* 左侧 vs. 表的相等比较器 */
} SubPlanState;

/*
 * DomainConstraintState - 在 CoerceToDomain 期间检查的一个项目
 *
 * 注意：我们将其视为 ExprState 树的一部分，因此我们给它
 * 一个遵循 xxxState 约定的名称。但没有直接关联的计划树节点。
 */
typedef enum DomainConstraintType
{
	DOM_CONSTRAINT_NOTNULL,
	DOM_CONSTRAINT_CHECK
} DomainConstraintType;

typedef struct DomainConstraintState
{
	NodeTag		type;
	DomainConstraintType constrainttype;	/* 约束类型 */
	char	   *name;			/* 约束名称（用于错误消息） */
	Expr	   *check_expr;		/* 对于 CHECK，一个布尔表达式 */
	ExprState  *check_exprstate;	/* check_expr 的评估状态，或 NULL */
} DomainConstraintState;


/* ----------------------------------------------------------------
 *				 执行器状态树
 *
 * 一个正在执行的查询有一个 PlanState 树，与描述计划的 Plan 树平行。
 * ----------------------------------------------------------------
 */

/* ----------------
 *	 ExecProcNodeMtd
 *
 * 这是 ExecProcNode 调用的方法，用于从执行节点返回下一个元组。
 * 如果没有更多元组可用，它返回 NULL 或一个空的 TupleTableSlot。
 * ----------------
 */
typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate);

/* ----------------
 *		PlanState 节点
 *
 * 我们实际上从未实例化任何 PlanState 节点；这只是所有 PlanState 类型节点的通用
 * 抽象超类。
 * ----------------
 */
typedef struct PlanState
{
	NodeTag		type;

	Plan	   *plan;			/* 相关的计划节点 */

	EState	   *state;			/* 在执行时，单个节点的状态指向整个顶级计划的一个 EState */

	ExecProcNodeMtd ExecProcNode;	/* 返回下一个元组的函数 */
	ExecProcNodeMtd ExecProcNodeReal;	/* 实际函数，如果上述为
										 * 包装器 */

	Instrumentation *instrument;	/* 此节点的可选运行时统计信息 */
	WorkerInstrumentation *worker_instrument;	/* 每个工作线程的仪器化 */

	/* 每个工作线程的 JIT 仪器化 */
	struct SharedJitInstrumentation *worker_jit_instrument;

	/*
	 * 所有计划类型的公共结构数据。这些对子状态树的链接与
	 * 相关计划树中的链接平行（除了不存在于计划树中的
	 * subPlan 列表）。
	 */
	ExprState  *qual;			/* 布尔条件 */
	struct PlanState *lefttree; /* 输入计划树(s) */
	struct PlanState *righttree;

	List	   *initPlan;		/* 初始化 SubPlanState 节点（无相关表达式
								 * 子选择） */
	List	   *subPlan;		/* SubPlanState nodes in my expressions */

	/*
	 * 用于管理参数变更驱动的重新扫描的状态
	 */
	Bitmapset  *chgParam;		/* 已更改参数的 ID 集合 */

	/*
	 * 大多数节点类型所需的其他运行时状态。
	 */
	TupleDesc	ps_ResultTupleDesc; /* 节点的返回类型 */
	TupleTableSlot *ps_ResultTupleSlot; /* 我的结果元组的插槽 */
	ExprContext *ps_ExprContext;	/* 节点的表达式评估上下文 */
	ProjectionInfo *ps_ProjInfo;	/* 用于进行元组投影的信息 */

	bool		async_capable;	/* 如果节点是异步能力，则为 true */

	/*
	 * 如果已知，则为扫描槽的描述符。这有点 hack，但否则
	 * 就很难在表达式编译时基于描述符进行优化，
	 * 而不对所有执行器节点编码知识。
	 */
	TupleDesc	scandesc;

	/*
	 * 为具有此状态作为父的表达式上下文定义内部、外部和扫描槽的槽类型。
	 * 如果 *opsset 被设置，则 *opsfixed 表示 *ops 是否保证是
	 * 使用的槽的类型。这意味着在评估表达式时， 
	 * 对应的 ExprContext.ecxt_*tuple 中的每个槽将指向该类型的槽。
	 * 如果 *opsfixed 为 false，但 *ops 被设置，
	 * 则表示最可能的槽类型。
	 *
	 * scan* 字段由 ExecInitScanTupleSlot() 设置。如果没有
	 * 调用，节点可以自己初始化字段。
	 *
	 * 如果 outer/inneropsset 为 false，则使用 ExecGetResultSlotOps()
	 * 在 ->righttree/lefttree 上按需推断信息，
	 * 使用相应节点的 resultops* 字段。
	 *
	 * 当使用 ExecInitResultSlot 时，会自动设置 result* 字段
	 * （无论是直接还是当槽由 ExecAssignScanProjectionInfo() /
	 * ExecConditionalAssignProjectionInfo() 创建时）。如果不需要投影，
	 * ExecConditionalAssignProjectionInfo() 将这些字段默认设置为扫描
	 * 操作。
	 */
	const TupleTableSlotOps *scanops;
	const TupleTableSlotOps *outerops;
	const TupleTableSlotOps *innerops;
	const TupleTableSlotOps *resultops;
	bool		scanopsfixed;
	bool		outeropsfixed;
	bool		inneropsfixed;
	bool		resultopsfixed;
	bool		scanopsset;
	bool		outeropsset;
	bool		inneropsset;
	bool		resultopsset;
} PlanState;

/* ----------------
 *	这些被定义以避免 "left" 和 "right" 以及 "inner" 和 "outer"
 * 之间的混淆问题。惯例是 "left" 计划是 "outer" 计划，而 "right" 计划是
 * 内部计划，但这些使代码更具可读性。
 * ----------------
 */
#define innerPlanState(node)		(((PlanState *)(node))->righttree)
#define outerPlanState(node)		(((PlanState *)(node))->lefttree)

/* 用于访问某些监测计数器的宏 */
#define InstrCountTuples2(node, delta) \
	do { \
		if (((PlanState *)(node))->instrument) \
			((PlanState *)(node))->instrument->ntuples2 += (delta); \
	} while (0)
#define InstrCountFiltered1(node, delta) \
	do { \
		if (((PlanState *)(node))->instrument) \
			((PlanState *)(node))->instrument->nfiltered1 += (delta); \
	} while(0)
#define InstrCountFiltered2(node, delta) \
	do { \
		if (((PlanState *)(node))->instrument) \
			((PlanState *)(node))->instrument->nfiltered2 += (delta); \
	} while(0)

/*
 * EPQState 是用于在候选元组上执行 EvalPlanQual 重新检查的状态
 * 例如在 ModifyTable 或 LockRows 中。
 *
 * 为执行 EPQ 创建一个单独的 EState（存储在 ->recheckestate 中），
 * 它共享一些资源，例如范围表，与主查询的 EState（存储在 ->parentestate 中）。
 * 需要重新检查的计划的（子）树（在 ->plan 中）被单独初始化（到
 * ->recheckplanstate），但与主查询中的相应节点共享计划节点。
 * 在该单独的执行器树中的扫描节点被更改为仅返回
 * 相关表的当前感兴趣元组。这些元组要么由调用者提供
 * （使用 EvalPlanQualSlot），和/或通过行标记机制找到
 * （EPQ 机制本身的非锁定行标记，由调用者提供锁定的）。
 *
 * 虽然要检查的计划可以通过 EvalPlanQualSetPlan() 更改，但
 * 所有这样的计划都需要共享相同的 EState。
 */
typedef struct EPQState
{
	/* 这些由 EvalPlanQualInit() 初始化，后续不会更改： */
	EState	   *parentestate;	/* 主查询的 EState */
	int			epqParam;		/* 强制扫描节点重新评估的参数 ID */
	struct EPQStateExtra *epqExtra; /* 避免 ABI 破坏的扩展指针 */

	/*
	 * relsubs_slot[scanrelid - 1] 保存要由
	 * 扫描节点为 scanrelid'th RT 索引返回的 EPQ 测试元组，以便不进行
	 * 实际的表扫描。调用者应使用 EvalPlanQualSlot() 获取
	 * 这些槽。
	 */
	TupleTableSlot **relsubs_slot;

	/*
	 * 由 EvalPlanQualInit() 初始化，稍后可以使用
	 * EvalPlanQualSetPlan() 更改：
	 */

	Plan	   *plan;			/* 要执行的计划树 */
	List	   *arowMarks;		/* ExecAuxRowMarks（仅限非锁定） */


	/*
	 * 要重新检查的原始输出元组。由
	 * EvalPlanQualSetSlot() 设置，在可能调用 EvalPlanQualNext() 或 EvalPlanQual() 之前。
	 */
	TupleTableSlot *origslot;


	/* 由 EvalPlanQualBegin() 初始化或重置： */

	EState	   *recheckestate;	/* EPQ 执行的 EState，见上文 */

	/*
	 * 可以按需通过 EvalPlanQualFetchRowMark() 获取的行标记，
	 * 以 scanrelid - 1 为索引。仅限非锁定行标记。
	 */
	ExecAuxRowMark **relsubs_rowmark;

	/*
	 * relsubs_done[scanrelid - 1] 如果此目标关系没有 EPQ 元组或
	 * 已在当前 EvalPlanQual 测试中的当前扫描中被提取，则为 true。
	 */
	bool	   *relsubs_done;

	PlanState  *recheckplanstate;	/* EPQ 特定的执行节点，用于 ->plan */
} EPQState;


/*
 * 为了避免在后支中 EPQState 大小的 ABI 破坏性更改，
 * 我们在 EvalPlanQualInit 中创建其中一个。
 */
typedef struct EPQStateExtra
{
	List	   *resultRelations;	/* RT 索引的整数列表，或 NIL */
	List	   *tuple_table;	/* tuple table for relsubs_slot */

	/*
	 * relsubs_blocked[scanrelid - 1] 如果在当前 EvalPlanQual 测试期间
	 * 该目标关系没有 EPQ 元组，则为 true。我们为所有在 resultRelations 中列出的 relid
	 * 保持这些标志设置，但暂时清除实际传递给 EvalPlanQual() 的关系的标志。
	 */
	bool	   *relsubs_blocked;
} EPQStateExtra;

/* ----------------
 *	 ResultState information
 * ----------------
 */
typedef struct ResultState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	ExprState  *resconstantqual;
	bool		rs_done;		/* 我们完成了吗？ */
	bool		rs_checkqual;	/* 我们需要检查条件吗？ */
} ResultState;

/* ----------------
 *	 ProjectSetState information
 *
 * 注意：至少有一个 "elems" 将是 SetExprState；其余为
 * 普通 ExprStates。
 * ----------------
 */
typedef struct ProjectSetState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	Node	  **elems;			/* 表达式状态数组 */
	ExprDoneCond *elemdone;		/* 每个 SRF 的完成状态数组 */
	int			nelems;			/* elemdone[] 数组的长度 */
	bool		pending_srf_tuples; /* 仍在评估 tlist 中的 srfs 吗？ */
	MemoryContext argcontext;	/* SRF 参数上下文 */
} ProjectSetState;


/* mt_merge_subcommands 的标志 */
#define MERGE_INSERT	0x01
#define MERGE_UPDATE	0x02
#define MERGE_DELETE	0x04

/* ----------------
 *	 ModifyTableState information
 * ----------------
 */
typedef struct ModifyTableState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	CmdType		operation;		/* INSERT、UPDATE、DELETE 或 MERGE */
	bool		canSetTag;		/* 我们设置命令标签/es_processed 吗？ */
	bool		mt_done;		/* 我们完成了吗？ */
	int			mt_nrels;		/* resultRelInfo[] 中的条目数 */
	ResultRelInfo *resultRelInfo;	/* 关于目标关系的信息 */

	/*
	 * 原始语句中提到的目标关系，用于触发语句级触发器
	 * 和作为元组路由的根。 （这可能指向 resultRelInfo[] 条目之一，
	 * 但它也可以是一个独特的结构。）
	 */
	ResultRelInfo *rootResultRelInfo;

	EPQState	mt_epqstate;	/* 用于评估 EvalPlanQual 重新检查 */
	bool		fireBSTriggers; /* 我们需要触发语句触发器吗？ */

	/*
	 * 这些字段用于继承的 UPDATE 和 DELETE，
	 * 以跟踪给定元组来自哪个目标关系。如果目标关系数量较多，
	 * 我们使用哈希表将表 OID 转换为 resultRelInfo[] 索引；
	 * 否则 mt_resultOidHash 为 NULL。
	 */
	int			mt_resultOidAttno;	/* "tableoid" 垃圾属性的 resno */
	Oid			mt_lastResultOid;	/* 最近看到的 tableoid 值 */
	int			mt_lastResultIndex; /* resultRelInfo[] 中的对应索引 */
	HTAB	   *mt_resultOidHash;	/* 可选哈希表以加速查找 */

	/*
	 * 在对分区表执行 UPDATE 期间，存储元组的插槽
	 * 在根分区表的行类型中。
	 */
	TupleTableSlot *mt_root_tuple_slot;

	/* 元组路由支持信息 */
	struct PartitionTupleRouting *mt_partition_tuple_routing;

	/* 控制指定操作的过渡表填充 */
	struct TransitionCaptureState *mt_transition_capture;

	/* 控制 INSERT...ON CONFLICT UPDATE 的过渡表填充 */
	struct TransitionCaptureState *mt_oc_transition_capture;

	/* 标志显示哪些子命令存在 INS/UPD/DEL/DO NOTHING */
	int			mt_merge_subcommands;

	/* MERGE 的元组计数器 */
	double		mt_merge_inserted;
	double		mt_merge_updated;
	double		mt_merge_deleted;
} ModifyTableState;

/* ----------------
 *	 AppendState 信息
 *
 *		nplans				数组中有多少个计划
 *		whichplan			正在执行的同步计划（0 .. n-1）
 *							或特定的负值。请参见 nodeAppend.c。
 *		prune_state			允许从扫描中删除分区所需的详细信息，
 *							如果不可能则为 NULL。
 *		valid_subplans		用于运行时修剪的有效同步附加计划
 *							的索引。
 * ----------------
 */

struct AppendState;
typedef struct AppendState AppendState;
struct ParallelAppendState;
typedef struct ParallelAppendState ParallelAppendState;
struct PartitionPruneState;

struct AppendState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	PlanState **appendplans;	/* 我输入的 PlanStates 数组 */
	int			as_nplans;
	int			as_whichplan;
	bool		as_begun;		/* false 表示需要初始化 */
	Bitmapset  *as_asyncplans;	/* 异步计划的索引 */
	int			as_nasyncplans; /* 异步计划的数量 */
	AsyncRequest **as_asyncrequests;	/* AsyncRequests 数组 */
	TupleTableSlot **as_asyncresults;	/* 异步计划的未返回结果 */
	int			as_nasyncresults;	/* as_asyncresults 中有效条目的数量 */
	bool		as_syncdone;	/* 如果所有同步计划都以
								 * 异步模式完成，则为 true，否则为 false */
	int			as_nasyncremain;	/* 剩余的异步计划数量 */
	Bitmapset  *as_needrequest; /* 需要新请求的异步计划 */
	struct WaitEventSet *as_eventset;	/* 用于配置文件描述符等待事件的 WaitEventSet */
	int			as_first_partial_plan;	/* 包含第一个部分计划的 'appendplans' 的索引 */
	ParallelAppendState *as_pstate; /* 并行协调信息 */
	Size		pstate_len;		/* 并行协调信息的大小 */
	struct PartitionPruneState *as_prune_state;
	Bitmapset  *as_valid_subplans;
	Bitmapset  *as_valid_asyncplans;	/* 有效的异步计划索引 */
	bool		(*choose_next_subplan) (AppendState *);
};

/* ----------------
 *	 MergeAppendState 信息
 *
 *		nplans			数组中有多少个计划
 *		nkeys			排序键列的数量
 *		sortkeys		排序键的 SortSupport 表示
 *		slots			每个子计划的当前输出元组
 *		heap			活动元组的堆
 *		initialized		如果我们已经从每个子计划中获取了第一个元组，则为 true
 *		prune_state		允许从扫描中删除分区所需的详细信息，
 *						如果不可能则为 NULL。
 *		valid_subplans	用于运行时修剪的有效合并计划索引
 *						以进行扫描。
 * ----------------
 */
typedef struct MergeAppendState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	PlanState **mergeplans;		/* 我输入的 PlanStates 数组 */
	int			ms_nplans;
	int			ms_nkeys;
	SortSupport ms_sortkeys;	/* 长度为 ms_nkeys 的数组 */
	TupleTableSlot **ms_slots;	/* 长度为 ms_nplans 的数组 */
	struct binaryheap *ms_heap; /* 插槽索引的二进制堆 */
	bool		ms_initialized; /* 子计划是否已启动？ */
	struct PartitionPruneState *ms_prune_state;
	Bitmapset  *ms_valid_subplans;
} MergeAppendState;

/* ----------------
 *	 RecursiveUnionState 信息
 *
 *		RecursiveUnionState 用于执行递归联合。
 *
 *		recursing			当我们完成扫描非递归项时为 T
 *		intermediate_empty	T 如果 intermediate_table 当前为空
 *		working_table		工作表（待递归项扫描）
 *		intermediate_table	当前的递归输出（WT 的下一代）
 * ----------------
 */
typedef struct RecursiveUnionState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	bool		recursing;
	bool		intermediate_empty;
	Tuplestorestate *working_table;
	Tuplestorestate *intermediate_table;
	/* 其余字段在 UNION ALL 情况下未使用 */
	Oid		   *eqfuncoids;		/* 每个分组字段的相等函数 */
	FmgrInfo   *hashfunctions;	/* 每个分组字段的哈希函数 */
	MemoryContext tempContext;	/* 比较的短期上下文 */
	TupleHashTable hashtable;	/* 已见元组的哈希表 */
	MemoryContext tableContext; /* 包含哈希表的内存上下文 */
} RecursiveUnionState;

/* ----------------
 *	 BitmapAndState 信息
 * ----------------
 */
typedef struct BitmapAndState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	PlanState **bitmapplans;	/* 我输入的 PlanStates 数组 */
	int			nplans;			/* 输入计划的数量 */
} BitmapAndState;

/* ----------------
 *	 BitmapOrState 信息
 * ----------------
 */
typedef struct BitmapOrState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	PlanState **bitmapplans;	/* 我输入的 PlanStates 数组 */
	int			nplans;			/* 输入计划的数量 */
} BitmapOrState;

/* ----------------------------------------------------------------
 *				 扫描状态信息
 * ----------------------------------------------------------------
 */

/* ----------------
 *	 ScanState 信息
 *
 *		ScanState 扩展了 PlanState，用于表示
 *		对底层关系的扫描的节点类型。它也可以用于扫描
 *		底层计划节点输出的节点——在这种情况下，
 *		只有 ScanTupleSlot 实际上是有用的，它指的是从
 *		子计划中检索到的元组。
 *
 *		currentRelation    正在扫描的关系（如果没有则为 NULL）
 *		currentScanDesc    当前扫描描述符（如果没有则为 NULL）
 *		ScanTupleSlot	   指向元组表中保存扫描元组的插槽的指针
 * ----------------
 */
typedef struct ScanState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	Relation	ss_currentRelation;
	struct TableScanDescData *ss_currentScanDesc;
	TupleTableSlot *ss_ScanTupleSlot;
} ScanState;

/* ----------------
 *	 SeqScanState 信息
 * ----------------
 */
typedef struct SeqScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	Size		pscan_len;		/* 并行堆扫描描述符的大小 */
} SeqScanState;

/* ----------------
 *	 SampleScanState 信息
 * ----------------
 */
typedef struct SampleScanState
{
	ScanState	ss;
	List	   *args;			/* TABLESAMPLE 参数的表达式状态 */
	ExprState  *repeatable;		/* REPEATABLE 表达式的表达式状态 */
	/* 使用结构指针以避免在此包含 tsmapi.h */
	struct TsmRoutine *tsmroutine;	/* 表示 TABLESAMPLE 方法的描述符 */
	void	   *tsm_state;		/* 表示 TABLESAMPLE 方法可以在这里保持状态 */
	bool		use_bulkread;	/* 是否使用 bulkread 缓冲访问策略？ */
	bool		use_pagemode;	/* 是否使用逐页可见性检查？ */
	bool		begun;			/* false 意味着需要调用 BeginSampleScan */
	uint32		seed;			/* 随机种子 */
	int64		donetuples;		/* 已经返回的元组数量 */
	bool		haveblock;		/* 是否已经确定了采样的块 */
	bool		done;			/* 是否耗尽所有元组？ */
} SampleScanState;

/*
 * 这些结构存储有关没有简单
 * 常数右侧的索引条件的信息。有关讨论，请参见 ExecIndexBuildScanKeys()
 * 的注释。
 */
typedef struct
{
	struct ScanKeyData *scan_key;	/* 要放入的扫描键 */
	ExprState  *key_expr;		/* 评估以获取值的表达式 */
	bool		key_toastable;	/* 表达式的结果是可吐的 数据类型吗？ */
} IndexRuntimeKeyInfo;

typedef struct
{
	struct ScanKeyData *scan_key;	/* 要放入的扫描键 */
	ExprState  *array_expr;		/* 评估以获取数组值的表达式 */
	int			next_elem;		/* 要使用的下一个数组元素 */
	int			num_elems;		/* 当前数组值中的元素数量 */
	Datum	   *elem_values;	/* num_elems Datums 的数组 */
	bool	   *elem_nulls;		/* num_elems 的空值标志数组 */
} IndexArrayKeyInfo;

/* ----------------
 *	 IndexScanState 信息
 *
 *		indexqualorig	   indexqualorig 表达式的执行状态
 *		indexorderbyorig   indexorderbyorig 表达式的执行状态
 *		ScanKeys		   用于索引条件的 Skey 结构
 *		NumScanKeys		   ScanKeys 数量
 *		OrderByKeys		   用于索引排序操作符的 Skey 结构
 *		NumOrderByKeys	   OrderByKeys 的数量
 *		RuntimeKeys		   有关必须在运行时评估的 Skeys 的信息
 *		NumRuntimeKeys	   RuntimeKeys 的数量
 *		RuntimeKeysReady   如果运行时 Skeys 已被计算则为 true
 *		RuntimeContext	   用于评估运行时 Skeys 的表达式上下文
 *		RelationDesc	   索引关系描述符
 *		ScanDesc		   索引扫描描述符
 *
 *		ReorderQueue	   由于重新检查而需要重新排序的元组
 *		ReachedEnd		   我们是否已经从索引中获取所有元组？
 *		OrderByValues	   最后获取的元组的 ORDER BY 表达式的值
 *		OrderByNulls	   OrderByValues 的空值标志
 *		SortSupport		   用于重新排序 ORDER BY 表达式
 *		OrderByTypByVals   ORDER BY 表达式的数据类型是按值传递吗？
 *		OrderByTypLens	   ORDER BY 表达式的数据类型的 typlens
 *		PscanLen		   并行索引扫描描述符的大小
 * ----------------
 */
typedef struct IndexScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *indexqualorig;
	List	   *indexorderbyorig;
	struct ScanKeyData *iss_ScanKeys;
	int			iss_NumScanKeys;
	struct ScanKeyData *iss_OrderByKeys;
	int			iss_NumOrderByKeys;
	IndexRuntimeKeyInfo *iss_RuntimeKeys;
	int			iss_NumRuntimeKeys;
	bool		iss_RuntimeKeysReady;
	ExprContext *iss_RuntimeContext;
	Relation	iss_RelationDesc;
	struct IndexScanDescData *iss_ScanDesc;

	/* 这些是在重新检查 ORDER BY 表达式排序时需要的 */
	pairingheap *iss_ReorderQueue;
	bool		iss_ReachedEnd;
	Datum	   *iss_OrderByValues;
	bool	   *iss_OrderByNulls;
	SortSupport iss_SortSupport;
	bool	   *iss_OrderByTypByVals;
	int16	   *iss_OrderByTypLens;
	Size		iss_PscanLen;
} IndexScanState;

/* ----------------
 *	 IndexOnlyScanState 信息
 *
 *		recheckqual		   recheckqual 表达式的执行状态
 *		ScanKeys		   用于索引条件的 Skey 结构
 *		NumScanKeys		   ScanKeys 的数量
 *		OrderByKeys		   用于索引排序操作符的 Skey 结构
 *		NumOrderByKeys	   OrderByKeys 的数量
 *		RuntimeKeys		   有关必须在运行时评估的 Skeys 的信息
 *		NumRuntimeKeys	   RuntimeKeys 的数量
 *		RuntimeKeysReady   如果运行时 Skeys 已被计算则为 true
 *		RuntimeContext	   用于评估运行时 Skeys 的表达式上下文
 *		RelationDesc	   索引关系描述符
 *		ScanDesc		   索引扫描描述符
 *		TableSlot		   用于保存从表中获取的元组的插槽
 *		VMBuffer		   用于可见性映射测试的缓冲区（如果有的话）
 *		PscanLen		   并行索引仅扫描描述符的大小
 *		NameCStringAttNums 名称类型列的 attnums，用于填充 NAMEDATALEN
 *		NameCStringCount   NameCStringAttNums 数组中的元素数量
 * ----------------
 */
typedef struct IndexOnlyScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *recheckqual;
	struct ScanKeyData *ioss_ScanKeys;
	int			ioss_NumScanKeys;
	struct ScanKeyData *ioss_OrderByKeys;
	int			ioss_NumOrderByKeys;
	IndexRuntimeKeyInfo *ioss_RuntimeKeys;
	int			ioss_NumRuntimeKeys;
	bool		ioss_RuntimeKeysReady;
	ExprContext *ioss_RuntimeContext;
	Relation	ioss_RelationDesc;
	struct IndexScanDescData *ioss_ScanDesc;
	TupleTableSlot *ioss_TableSlot;
	Buffer		ioss_VMBuffer;
	Size		ioss_PscanLen;
	AttrNumber *ioss_NameCStringAttNums;
	int			ioss_NameCStringCount;
} IndexOnlyScanState;

/* ----------------
 *	 BitmapIndexScanState 信息
 *
 *		result			   返回输出的位图，或 NULL
 *		ScanKeys		   索引资格的 Skey 结构
 *		NumScanKeys		   ScanKeys 的数量
 *		RuntimeKeys		   关于必须在运行时评估的 Skeys 的信息
 *		NumRuntimeKeys	   RuntimeKeys 的数量
 *		ArrayKeys		   来自 ScalarArrayOpExprs 的 Skeys 信息
 *		NumArrayKeys	   ArrayKeys 的数量
 *		RuntimeKeysReady   如果运行时 Skeys 已计算，则为真
 *		RuntimeContext	   评估运行时 Skeys 的表达式上下文
 *		RelationDesc	   索引关系描述符
 *		ScanDesc		   索引扫描描述符
 * ----------------
 */
typedef struct BitmapIndexScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	TIDBitmap  *biss_result;
	struct ScanKeyData *biss_ScanKeys;
	int			biss_NumScanKeys;
	IndexRuntimeKeyInfo *biss_RuntimeKeys;
	int			biss_NumRuntimeKeys;
	IndexArrayKeyInfo *biss_ArrayKeys;
	int			biss_NumArrayKeys;
	bool		biss_RuntimeKeysReady;
	ExprContext *biss_RuntimeContext;
	Relation	biss_RelationDesc;
	struct IndexScanDescData *biss_ScanDesc;
} BitmapIndexScanState;

/* ----------------
 *	 SharedBitmapState 信息
 *
 *		BM_INITIAL		TIDBitmap 创建尚未开始，因此第一个看到此状态的工作者
 *						将状态设置为 BM_INPROGRESS，并且该进程将负责创建
 *						TIDBitmap。
 *		BM_INPROGRESS	TIDBitmap 创建正在进行；工作者需要
 *						等待直到完成。
 *		BM_FINISHED		TIDBitmap 创建完成，因此现在所有工作者可以
 *						继续迭代 TIDBitmap。
 * ----------------
 */
typedef enum
{
	BM_INITIAL,
	BM_INPROGRESS,
	BM_FINISHED
} SharedBitmapState;

/* ----------------
 *	 ParallelBitmapHeapState 信息
 *		tbmiterator				当前页面的扫描迭代器
 *		prefetch_iterator		当前页面前的预取迭代器
 *		mutex					预取变量和状态的互斥
 *		prefetch_pages			预取迭代器超过当前页面的页数
 *		prefetch_target			当前目标预取距离
 *		state					TIDBitmap 的当前状态
 *		cv						条件等待变量
 *		phs_snapshot_data		共享给工作者的快照数据
 * ----------------
 */
typedef struct ParallelBitmapHeapState
{
	dsa_pointer tbmiterator;
	dsa_pointer prefetch_iterator;
	slock_t		mutex;
	int			prefetch_pages;
	int			prefetch_target;
	SharedBitmapState state;
	ConditionVariable cv;
	char		phs_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
} ParallelBitmapHeapState;

/* ----------------
 *	 BitmapHeapScanState 信息
 *
 *		bitmapqualorig	   bitmapqualorig 表达式的执行状态
 *		tbm				   从子索引扫描获得的位图
 *		tbmiterator		   当前页面的扫描迭代器
 *		tbmres			   当前页数据
 *		can_skip_fetch	   我们可以在此扫描中潜在地跳过元组获取吗？
 *		return_empty_tuples 返回的空元组数量
 *		vmbuffer		   可见性图查找的缓冲区
 *		pvmbuffer		   与此类似，预取页面的缓冲区
 *		exact_pages		   检索的精确页面总数
 *		lossy_pages		   检索的丢失页面总数
 *		prefetch_iterator  当前页面前的预取迭代器
 *		prefetch_pages	   预取迭代器超过当前页面的页数
 *		prefetch_target    当前目标预取距离
 *		prefetch_maximum   prefetch_target 的最大值
 *		pscan_len		   并行位图的共享内存大小
 *		initialized		   节点是否准备好迭代
 *		shared_tbmiterator	   共享迭代器
 *		shared_prefetch_iterator 共享的预取迭代器
 *		pstate			   并行位图扫描的共享状态
 * ----------------
 */
typedef struct BitmapHeapScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *bitmapqualorig;
	TIDBitmap  *tbm;
	TBMIterator *tbmiterator;
	TBMIterateResult *tbmres;
	bool		can_skip_fetch;
	int			return_empty_tuples;
	Buffer		vmbuffer;
	Buffer		pvmbuffer;
	long		exact_pages;
	long		lossy_pages;
	TBMIterator *prefetch_iterator;
	int			prefetch_pages;
	int			prefetch_target;
	int			prefetch_maximum;
	Size		pscan_len;
	bool		initialized;
	TBMSharedIterator *shared_tbmiterator;
	TBMSharedIterator *shared_prefetch_iterator;
	ParallelBitmapHeapState *pstate;
} BitmapHeapScanState;

/* ----------------
 *	 TidScanState 信息
 *
 *		tidexprs	   TidExpr 结构的列表（见 nodeTidscan.c）
 *		isCurrentOf    扫描是否具有 CurrentOfExpr 资格
 *		NumTids		   此扫描中的 tids 数量
 *		TidPtr		   当前获取的 tid 索引
 *		TidList		   评估的项目指针（大小为 NumTids 的数组）
 *		htup		   当前获取的元组（如果有的话）
 * ----------------
 */
typedef struct TidScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	List	   *tss_tidexprs;
	bool		tss_isCurrentOf;
	int			tss_NumTids;
	int			tss_TidPtr;
	ItemPointerData *tss_TidList;
	HeapTupleData tss_htup;
} TidScanState;

/* ----------------
 *	 TidRangeScanState 信息
 *
 *		trss_tidexprs		TidOpExpr 结构的列表（见 nodeTidrangescan.c）
 *		trss_mintid			扫描范围内的最小 TID
 *		trss_maxtid			扫描范围内的最大 TID
 *		trss_inScan			扫描当前是否正在进行？
 * ----------------
 */
typedef struct TidRangeScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	List	   *trss_tidexprs;
	ItemPointerData trss_mintid;
	ItemPointerData trss_maxtid;
	bool		trss_inScan;
} TidRangeScanState;

/* ----------------
 *	 SubqueryScanState 信息
 *
 *		SubqueryScanState 用于扫描范围表中的子查询。
 *		ScanTupleSlot 引用子查询的当前输出元组。
 * ----------------
 */
typedef struct SubqueryScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	PlanState  *subplan;
} SubqueryScanState;

/* ----------------
 *	 FunctionScanState 信息
 *
 *		函数节点用于扫描出现在 FROM 中的
 *		函数的结果（通常是返回集的函数）。
 *
 *		eflags				节点的能力标志
 *		ordinality			此扫描是否带有 ORDINALITY？
 *		simple				如果我们有 1 个函数且没有序号，则为真
 *		ordinal				当前序号列值
 *		nfuncs				正在执行的函数数量
 *		funcstates			每个函数的执行状态（在
 *							nodeFunctionscan.c 中为私有）
 *		argcontext			评估函数参数的内存上下文
 * ----------------
 */
struct FunctionScanPerFuncState;

typedef struct FunctionScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	int			eflags;
	bool		ordinality;
	bool		simple;
	int64		ordinal;
	int			nfuncs;
	struct FunctionScanPerFuncState *funcstates;	/* 长度为 nfuncs 的数组 */
	MemoryContext argcontext;
} FunctionScanState;


/* ----------------
 *	 ValuesScanState 信息
 *
 *		ValuesScan 节点用于扫描 VALUES 列表的结果
 *
 *		rowcontext			每个表达式列表的上下文
 *		exprlists			正在评估的表达式列表数组
 *		exprstatelists		表达式状态列表数组，仅针对 SubPlans
 *		array_len			上述数组的大小
 *		curr_idx			当前数组索引 (0 基础)
 *
 *	注意: ss.ps.ps_ExprContext 用于评估附加到节点的任何 qual 或投影
 *	表达式。我们创建了第二个 ExprContext，
 *	rowcontext，在其中构建每个 Values 子列表的执行者表达式状态。
 *	重置此上下文使我们能够摆脱每行的表达式
 *	状态，避免在长 values 列表中出现重大内存泄漏。
 *	然而，这对于包含 SubPlans 的子列表不起作用，因为 SubPlan 必须连接到外部计划树才能正常工作。
 *	因此，只对那些包含 SubPlans 的子列表，我们在执行者启动时进行表达式
 *	状态构建，并将这些指针存储在 exprstatelists[] 中。该数组中的 NULL 条目对应于按上述描述处理的简单
 *	子表达式。
 * ----------------
 */
typedef struct ValuesScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprContext *rowcontext;
	List	  **exprlists;
	List	  **exprstatelists;
	int			array_len;
	int			curr_idx;
} ValuesScanState;

/* ----------------
 *		TableFuncScanState 节点
 *
 * 用于 XMLTABLE 等表表达式函数。
 * ----------------
 */
typedef struct TableFuncScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *docexpr;		/* 文档表达式的状态 */
	ExprState  *rowexpr;		/* 行生成表达式的状态 */
	List	   *colexprs;		/* 列生成表达式的状态 */
	List	   *coldefexprs;	/* 列默认表达式的状态 */
	List	   *ns_names;		/* 与 TableFunc.ns_names 相同 */
	List	   *ns_uris;		/* 命名空间 URI 表达式的状态列表 */
	Bitmapset  *notnulls;		/* 每个输出列的可空性标志 */
	void	   *opaque;			/* 表构建器私有空间 */
	const struct TableFuncRoutine *routine; /* 表构建方法 */
	FmgrInfo   *in_functions;	/* 每列的输入函数 */
	Oid		   *typioparams;	/* 每列的typiparam */
	int64		ordinal;		/* 下一个要输出的行号 */
	MemoryContext perTableCxt;	/* 每个表的上下文 */
	Tuplestorestate *tupstore;	/* 输出元组存储 */
} TableFuncScanState;

/* ----------------
 *	 CteScanState 信息
 *
 *		CteScan 节点用于扫描 CommonTableExpr 查询。
 *
 * 多个 CteScan 节点可以从同一个 CTE 查询中读取。我们使用
 * 一个元组存储来保存已经从 CTE 查询中读取但尚未被所有读取者消费的行。
 * ----------------
 */
typedef struct CteScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	int			eflags;			/* 传递给元组存储的能力标志 */
	int			readptr;		/* 我的元组存储读取指针的索引 */
	PlanState  *cteplanstate;	/* CTE 查询本身的 PlanState */
	/* 链接到“领导者”CteScanState（可能是这个节点） */
	struct CteScanState *leader;
	/* 其余字段仅在“领导者”CteScanState 中有效 */
	Tuplestorestate *cte_table; /* 已经从 CTE 查询中读取的行 */
	bool		eof_cte;		/* 已到达 CTE 查询的末尾？ */
} CteScanState;

/* ----------------
 *	 NamedTuplestoreScanState 信息
 *
 *		NamedTuplestoreScan 节点用于扫描在查询执行之前创建和命名的 Tuplestore。一个例子是 AFTER 触发器的过渡表。
 *
 * 多个 NamedTuplestoreScan 节点可以从相同的 Tuplestore 中读取。
 * ----------------
 */
typedef struct NamedTuplestoreScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	int			readptr;		/* 我的元组存储读取指针的索引 */
	TupleDesc	tupdesc;		/* Tuplestore 中元组的格式 */
	Tuplestorestate *relation;	/* the rows */
} NamedTuplestoreScanState;

/* ----------------
 *	 WorkTableScanState 信息
 *
 *		WorkTableScan 节点用于扫描由 RecursiveUnion 节点创建的工作表。我们在执行器启动期间定位 RecursiveUnion 节点。
 * ----------------
 */
typedef struct WorkTableScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	RecursiveUnionState *rustate;
} WorkTableScanState;

/* ----------------
 *	 ForeignScanState 信息
 *
 *		ForeignScan 节点用于扫描外部数据表。
 * ----------------
 */
typedef struct ForeignScanState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *fdw_recheck_quals;	/* 原始资格不在 ss.ps.qual 中 */
	Size		pscan_len;		/* 并行协调信息的大小 */
	ResultRelInfo *resultRelInfo;	/* 结果关系信息，如果是 UPDATE 或 DELETE */
	/* 使用结构指针以避免在此处包含 fdwapi.h */
	struct FdwRoutine *fdwroutine;
	void	   *fdw_state;		/* 外部数据封装器可以在此保持状态 */
} ForeignScanState;

/* ----------------
 *	 CustomScanState 信息
 *
 *		CustomScan 节点用于在执行器内执行自定义代码。
 *
 * 核心代码必须避免假设 CustomScanState 仅仅与此处声明的结构大小相同；提供者允许将其作为更大结构中的第一个元素，并且通常需要这样做。该结构实际上是由与计划节点关联的 CreateCustomScanState 方法分配的。任何额外字段可以在那里初始化，或在 BeginCustomScan 方法中。
 * ----------------
 */
struct CustomExecMethods;

typedef struct CustomScanState
{
	ScanState	ss;
	uint32		flags;			/* CUSTOMPATH_* 标志的掩码，见
								 * nodes/extensible.h */
	List	   *custom_ps;		/* 子 PlanState 节点的列表（如果有） */
	Size		pscan_len;		/* 并行协调信息的大小 */
	const struct CustomExecMethods *methods;
} CustomScanState;

/* ----------------------------------------------------------------
 *				 Join 状态信息
 * ----------------------------------------------------------------
 */

/* ----------------
 *	 JoinState 信息
 *
 *		Join 计划状态节点的超类。
 * ----------------
 */
typedef struct JoinState
{
	PlanState	ps;
	JoinType	jointype;
	bool		single_match;	/* 如果在找到一个内部匹配后应该跳过到下一个外部元组，则为真 */
	ExprState  *joinqual;		/* JOIN 资格（除了 ps.qual 之外） */
} JoinState;

/* ----------------
 *	 NestLoopState 信息
 *
 *		NeedNewOuter	   如果在下一个调用时需要新的外部元组，则为真
 *		MatchedOuter	   如果找到了当前外部元组的连接匹配，则为真
 *		NullInnerTupleSlot 为左外连接准备的空元组
 * ----------------
 */
typedef struct NestLoopState
{
	JoinState	js;				/* 它的第一个字段是 NodeTag */
	bool		nl_NeedNewOuter;
	bool		nl_MatchedOuter;
	TupleTableSlot *nl_NullInnerTupleSlot;
} NestLoopState;

/* ----------------
 *	 MergeJoinState 信息
 *
 *		NumClauses		   可合并连接子句的数量
 *		Clauses			   每个可合并子句的信息
 *		JoinState		   ExecMergeJoin 状态机的当前状态
 *		SkipMarkRestore    如果可以跳过标记和还原操作，则为真
 *		ExtraMarks		   如果在内部扫描上发出额外的标记操作，则为真
 *		ConstFalseJoin	   如果具有常量假连接条件，则为真
 *		FillOuter		   如果无论如何都应该发出未连接的外部元组，则为真
 *		FillInner		   如果无论如何都应该发出未连接的内部元组，则为真
 *		MatchedOuter	   如果找到了当前外部元组的连接匹配，则为真
 *		MatchedInner	   如果找到了当前内部元组的连接匹配，则为真
 *		OuterTupleSlot	   当前外部元组的元组表中的槽
 *		InnerTupleSlot	   当前内部元组的元组表中的槽
 *		MarkedTupleSlot    标记元组在元组表中的槽
 *		NullOuterTupleSlot 为右外连接准备的空元组
 *		NullInnerTupleSlot 为左外连接准备的空元组
 *		OuterEContext	   计算外部元组连接值的工作区
 *		InnerEContext	   计算内部元组连接值的工作区
 * ----------------
 */
/* 在 nodeMergejoin.c 中私有： */
typedef struct MergeJoinClauseData *MergeJoinClause;

typedef struct MergeJoinState
{
	JoinState	js;				/* 它的第一个字段是 NodeTag */
	int			mj_NumClauses;
	MergeJoinClause mj_Clauses; /* 长度为 mj_NumClauses 的数组 */
	int			mj_JoinState;
	bool		mj_SkipMarkRestore;
	bool		mj_ExtraMarks;
	bool		mj_ConstFalseJoin;
	bool		mj_FillOuter;
	bool		mj_FillInner;
	bool		mj_MatchedOuter;
	bool		mj_MatchedInner;
	TupleTableSlot *mj_OuterTupleSlot;
	TupleTableSlot *mj_InnerTupleSlot;
	TupleTableSlot *mj_MarkedTupleSlot;
	TupleTableSlot *mj_NullOuterTupleSlot;
	TupleTableSlot *mj_NullInnerTupleSlot;
	ExprContext *mj_OuterEContext;
	ExprContext *mj_InnerEContext;
} MergeJoinState;


/* ----------------
 *	 HashJoinState 信息
 *
 *		hashclauses				hashjoin 条件的原始形式
 *		hj_OuterHashKeys		hashjoin 条件中的外部哈希键
 *		hj_HashOperators		hashjoin 条件中的连接操作符
 *		hj_HashTable			hashjoin 的哈希表
 *								（如果尚未构建表，则为 NULL）
 *		hj_CurHashValue			当前外部元组的哈希值
 *		hj_CurBucketNo			当前外部元组的常规桶编号
 *		hj_CurSkewBucketNo		当前外部元组的偏斜桶编号
 *		hj_CurTuple				与当前外部元组匹配的最后一个内部元组
 *								，如果开始搜索则为 NULL
 *								（如果 OuterTupleSlot 为空，则
 *								hj_CurXXX 变量未定义！）
 *		hj_OuterTupleSlot		外部元组的元组槽
 *		hj_HashTupleSlot		内部（哈希）元组的元组槽
 *		hj_NullOuterTupleSlot	为右/全外连接准备的空元组
 *		hj_NullInnerTupleSlot	为左/全外连接准备的空元组
 *		hj_FirstOuterTupleSlot	从外部计划中检索的第一个元组
 *		hj_JoinState			ExecHashJoin 状态机的当前状态
 *		hj_MatchedOuter			如果找到当前外部的连接匹配则为真
 *		hj_OuterNotEmpty		如果已知外部关系不为空则为真
 * ----------------
 */

/* 这些结构在 executor/hashjoin.h 中定义： */
typedef struct HashJoinTupleData *HashJoinTuple;
typedef struct HashJoinTableData *HashJoinTable;

typedef struct HashJoinState
{
	JoinState	js;				/* 它的第一个字段是 NodeTag */
	ExprState  *hashclauses;
	List	   *hj_OuterHashKeys;	/* ExprState 节点列表 */
	List	   *hj_HashOperators;	/* 操作符 OID 列表 */
	List	   *hj_Collations;
	HashJoinTable hj_HashTable;
	uint32		hj_CurHashValue;
	int			hj_CurBucketNo;
	int			hj_CurSkewBucketNo;
	HashJoinTuple hj_CurTuple;
	TupleTableSlot *hj_OuterTupleSlot;
	TupleTableSlot *hj_HashTupleSlot;
	TupleTableSlot *hj_NullOuterTupleSlot;
	TupleTableSlot *hj_NullInnerTupleSlot;
	TupleTableSlot *hj_FirstOuterTupleSlot;
	int			hj_JoinState;
	bool		hj_MatchedOuter;
	bool		hj_OuterNotEmpty;
} HashJoinState;


/* ----------------------------------------------------------------
 *				 材料化状态信息
 * ----------------------------------------------------------------
 */

/* ----------------
 *	 MaterialState 信息
 *
 *		materialize 节点用于将子计划的结果物化到临时文件中。
 *
 *		ss.ss_ScanTupleSlot 指的是底层计划的输出。
 * ----------------
 */
typedef struct MaterialState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	int			eflags;			/* 传递给元组存储的能力标志 */
	bool		eof_underlying; /* 是否到达底层计划的终点？ */
	Tuplestorestate *tuplestorestate;
} MaterialState;

struct MemoizeEntry;
struct MemoizeTuple;
struct MemoizeKey;

typedef struct MemoizeInstrumentation
{
	uint64		cache_hits;		/* 我们找到缓存的扫描参数值的重扫描次数 */
	uint64		cache_misses;	/* 我们未找到缓存的扫描参数值的重扫描次数。 */
	uint64		cache_evictions;	/* 由于需要释放内存而移除的缓存条目数量 */
	uint64		cache_overflows;	/* 由于无法释放足够的空间以存储当前扫描的元组而绕过缓存的次数。 */
	uint64		mem_peak;		/* 峰值内存使用量（以字节为单位） */
} MemoizeInstrumentation;

/* ----------------
 *	 每个工作者的共享内存容器，用于 memoize 信息
 * ----------------
 */
typedef struct SharedMemoizeInfo
{
	int			num_workers;
	MemoizeInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
} SharedMemoizeInfo;

/* ----------------
 *	 MemoizeState 信息
 *
 *		memoize 节点用于缓存来自参数化扫描的最近和常见结果。
 * ----------------
 */
typedef struct MemoizeState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	int			mstatus;		/* ExecMemoize 状态机的值 */
	int			nkeys;			/* 缓存键的数量 */
	struct memoize_hash *hashtable; /* 缓存条目的哈希表 */
	TupleDesc	hashkeydesc;	/* 缓存键的元组描述符 */
	TupleTableSlot *tableslot;	/* 现有缓存条目的最小元组槽 */
	TupleTableSlot *probeslot;	/* 用于哈希查找的虚拟槽 */
	ExprState  *cache_eq_expr;	/* 比较执行参数与哈希键 */
	ExprState **param_exprs;	/* 包含此节点参数的表达式 */
	FmgrInfo   *hashfunctions;	/* 哈希函数的查找数据，大小为 nkeys */
	Oid		   *collations;		/* 用于比较的排序大小 nkeys */
	uint64		mem_used;		/* 缓存使用的内存字节数 */
	uint64		mem_limit;		/* 缓存的内存限制（字节） */
	MemoryContext tableContext; /* 用于存储缓存数据的内存上下文 */
	dlist_head	lru_list;		/* 最近最少使用条目列表 */
	struct MemoizeTuple *last_tuple;	/* 用于指向在缓存命中期间返回的最后一个元组和填充缓存时最后存储的元组。 */
	struct MemoizeEntry *entry; /* 'last_tuple' 属于的条目，或者如果 'last_tuple' 为 NULL 则为 NULL。 */
	bool		singlerow;		/* 如果在缓存第一个元组后要将缓存条目标记为完成，则为真。 */
	bool		binary_mode;	/* 当缓存键应逐位比较时为真，使用哈希相等操作时为假 */
	MemoizeInstrumentation stats;	/* 执行统计信息 */
	SharedMemoizeInfo *shared_info; /* 并行工作者的统计信息 */
	Bitmapset  *keyparamids;	/* 属于 param_exprs 的表达式的 Param->paramids */
} MemoizeState;

/* ----------------
 *	 在按多个键执行排序时，输入数据集可能已根据这些键的前缀进行排序，我们称这些为 "预排序键"。
 *	 PresortedKeyData 表示关于这样的一个键的信息。
 * ----------------
 */
typedef struct PresortedKeyData
{
	FmgrInfo	flinfo;			/* 比较函数信息 */
	FunctionCallInfo fcinfo;	/* 比较函数调用信息 */
	OffsetNumber attno;			/* 元组中的属性编号 */
} PresortedKeyData;

/* ----------------
 *	 每个工作者的共享内存容器，用于排序信息
 * ----------------
 */
typedef struct SharedSortInfo
{
	int			num_workers;
	TuplesortInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
} SharedSortInfo;

/* ----------------
 *	 SortState 信息
 * ----------------
 */
typedef struct SortState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	bool		randomAccess;	/* 需要随机访问以排序输出吗？ */
	bool		bounded;		/* 结果集是有限的吗？ */
	int64		bound;			/* 如果有限，需要多少元组 */
	bool		sort_Done;		/* 排序完成了吗？ */
	bool		bounded_Done;	/* 我们进行排序时的有限值 */
	int64		bound_Done;		/* 我们进行排序时的边界值 */
	void	   *tuplesortstate; /* tuplesort.c 的私有状态 */
	bool		am_worker;		/* 我们是一个工作者吗？ */
	bool		datumSort;		/* 进行数据排序而不是元组排序？ */
	SharedSortInfo *shared_info;	/* 每个工作者一个条目 */
} SortState;

/* ----------------
 *	 IncrementalSort 的仪器信息
 * ----------------
 */
typedef struct IncrementalSortGroupInfo
{
	int64		groupCount;
	int64		maxDiskSpaceUsed;
	int64		totalDiskSpaceUsed;
	int64		maxMemorySpaceUsed;
	int64		totalMemorySpaceUsed;
	bits32		sortMethods;	/* TuplesortMethod 的位掩码 */
} IncrementalSortGroupInfo;

typedef struct IncrementalSortInfo
{
	IncrementalSortGroupInfo fullsortGroupInfo;
	IncrementalSortGroupInfo prefixsortGroupInfo;
} IncrementalSortInfo;

/* ----------------
 *	 每个工作者增量排序信息的共享内存容器
 * ----------------
 */
typedef struct SharedIncrementalSortInfo
{
	int			num_workers;
	IncrementalSortInfo sinfo[FLEXIBLE_ARRAY_MEMBER];
} SharedIncrementalSortInfo;

/* ----------------
 *	 IncrementalSortState 信息
 * ----------------
 */
typedef enum
{
	INCSORT_LOADFULLSORT,
	INCSORT_LOADPREFIXSORT,
	INCSORT_READFULLSORT,
	INCSORT_READPREFIXSORT,
} IncrementalSortExecutionStatus;

typedef struct IncrementalSortState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	bool		bounded;		/* 结果集是有限的吗？ */
	int64		bound;			/* 如果有限，需要多少元组 */
	bool		outerNodeDone;	/* 从外部节点获取元组完成 */
	int64		bound_Done;		/* 我们进行排序时的边界值 */
	IncrementalSortExecutionStatus execution_status;
	int64		n_fullsort_remaining;
	Tuplesortstate *fullsort_state; /* tuplesort.c 的私有状态 */
	Tuplesortstate *prefixsort_state;	/* tuplesort.c 的私有状态 */
	/* 输入路径已经排序的键 */
	PresortedKeyData *presorted_keys;

	IncrementalSortInfo incsort_info;

	/* 定义组内预排序键值的枢轴元组的槽 */
	TupleTableSlot *group_pivot;
	TupleTableSlot *transfer_tuple;
	bool		am_worker;		/* 我们是一个工作者吗？ */
	SharedIncrementalSortInfo *shared_info; /* 每个工作者一个条目 */
} IncrementalSortState;

/* ---------------------
 *	GroupState 信息
 * ---------------------
 */
typedef struct GroupState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	ExprState  *eqfunction;		/* 相等函数 */
	bool		grp_done;		/* 指示 Group 扫描的完成 */
} GroupState;

/* ---------------------
 *	每个工作者的聚合信息
 * ---------------------
 */
typedef struct AggregateInstrumentation
{
	Size		hash_mem_peak;	/* 峰值哈希表内存使用量 */
	uint64		hash_disk_used; /* 使用的磁盘空间（kB） */
	int			hash_batches_used;	/* 在整个执行过程中使用的批次 */
} AggregateInstrumentation;

/* ----------------
 *	 每个工作者的聚合信息的共享内存容器
 * ----------------
 */
typedef struct SharedAggInfo
{
	int			num_workers;
	AggregateInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
} SharedAggInfo;

/* ---------------------
 *	AggState 信息
 *
 *	ss.ss_ScanTupleSlot 指的是底层计划的输出。
 *
 *	注意：ss.ps.ps_ExprContext 包含 ecxt_aggvalues 和
 *	ecxt_aggnulls 数组，这些数组在评估 Agg 节点的输出元组时
 *	保存当前输入组的计算聚合值。我们创建了一个第二个 ExprContext，tmpcontext，
 *	用于评估输入表达式并运行聚合过渡函数。
 * ---------------------
 */
/* 这些结构体在 nodeAgg.c 中是私有的： */
typedef struct AggStatePerAggData *AggStatePerAgg;
typedef struct AggStatePerTransData *AggStatePerTrans;
typedef struct AggStatePerGroupData *AggStatePerGroup;
typedef struct AggStatePerPhaseData *AggStatePerPhase;
typedef struct AggStatePerHashData *AggStatePerHash;

typedef struct AggState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */
	List	   *aggs;			/* targetlist 和 quals 中的所有 Aggref 节点 */
	int			numaggs;		/* 列表的长度（可以为零！） */
	int			numtrans;		/* pertrans 项目数量 */
	AggStrategy aggstrategy;	/* 策略模式 */
	AggSplit	aggsplit;		/* agg-拆分模式，见 nodes.h */
	AggStatePerPhase phase;		/* 指向当前阶段数据的指针 */
	int			numphases;		/* 阶段数量（包括阶段 0） */
	int			current_phase;	/* 当前阶段编号 */
	AggStatePerAgg peragg;		/* 每个 Aggref 信息 */
	AggStatePerTrans pertrans;	/* 每个 Trans 状态信息 */
	ExprContext *hashcontext;	/* 用于长期数据的 econtexts（哈希表） */
	ExprContext **aggcontexts;	/* 用于长期数据的 econtexts（每个 GS） */
	ExprContext *tmpcontext;	/* 输入表达式的 econtext */
#define FIELDNO_AGGSTATE_CURAGGCONTEXT 14
	ExprContext *curaggcontext; /* 当前活动的 aggcontext */
	AggStatePerAgg curperagg;	/* 当前活动的聚合，如果有的话 */
#define FIELDNO_AGGSTATE_CURPERTRANS 16
	AggStatePerTrans curpertrans;	/* 当前活动的 trans 状态，如果有的话 */
	bool		input_done;		/* 指示输入结束 */
	bool		agg_done;		/* 指示 Agg 扫描完成 */
	int			projected_set;	/* 最后投影的分组集 */
#define FIELDNO_AGGSTATE_CURRENT_SET 20
	int			current_set;	/* 当前正在评估的分组集 */
	Bitmapset  *grouped_cols;	/* 当前投影中的分组列 */
	List	   *all_grouped_cols;	/* 所有分组列的降序列表 */
	Bitmapset  *colnos_needed;	/* 从外部计划中需要的所有列 */
	int			max_colno_needed;	/* 从外部计划中需要的最高列号 */
	bool		all_cols_needed;	/* 外部计划中的所有列都需要吗？ */
	/* 这些字段用于分组集阶段数据 */
	int			maxsets;		/* 任一阶段中的最大集数 */
	AggStatePerPhase phases;	/* 所有阶段的数组 */
	Tuplesortstate *sort_in;	/* 输入已排序到阶段 > 1 */
	Tuplesortstate *sort_out;	/* 输入在这里复制以供下一个阶段使用 */
	TupleTableSlot *sort_slot;	/* 排序结果的槽位 */
	/* 这些字段在 AGG_PLAIN 和 AGG_SORTED 模式中使用： */
	AggStatePerGroup *pergroups;	/* 分组集索引数组、每组的指针 */
	HeapTuple	grp_firstTuple; /* 当前组的第一个元组的副本 */
	/* 这些字段在 AGG_HASHED 和 AGG_MIXED 模式下使用： */
	bool		table_filled;	/* 哈希表已经填充完毕吗？ */
	int			num_hashes;
	MemoryContext hash_metacxt; /* 哈希表本身的内存 */
	struct LogicalTapeSet *hash_tapeset;	/* 用于哈希溢出磁带的带集 */
	struct HashAggSpill *hash_spills;	/* 每个分组集的 HashAggSpill，
										 * 仅在第一次遍历期间存在 */
	TupleTableSlot *hash_spill_rslot;	/* 用于读取溢出文件 */
	TupleTableSlot *hash_spill_wslot;	/* 用于写入溢出文件 */
	List	   *hash_batches;	/* 剩余待处理的哈希批次 */
	bool		hash_ever_spilled;	/* 这次执行期间是否曾经溢出？ */
	bool		hash_spill_mode;	/* 我们在当前批次中达到了限制，
										 * 必须不创建新组 */
	Size		hash_mem_limit; /* 溢出哈希表之前的限制 */
	uint64		hash_ngroups_limit; /* 溢出哈希表之前的限制 */
	int			hash_planned_partitions;	/* 第一次遍历中计划的分区数 */
	double		hashentrysize;	/* 执行期间修订的估计 */
	Size		hash_mem_peak;	/* 峰值哈希表内存使用量 */
	uint64		hash_ngroups_current;	/* 当前所有哈希表中的组数 */
	uint64		hash_disk_used; /* 使用的磁盘空间（kB） */
	int			hash_batches_used;	/* 在整个执行过程中使用的批次 */

	AggStatePerHash perhash;	/* 每个哈希表数据的数组 */
	AggStatePerGroup *hash_pergroup;	/* 分组集索引数组、每组的指针 */

	/* 支持评估聚合输入表达式： */
#define FIELDNO_AGGSTATE_ALL_PERGROUPS 53
	AggStatePerGroup *all_pergroups;	/* 从第一个 ->每组，接着
										 * ->每组哈希的数组 */
	ProjectionInfo *combinedproj;	/* 投影机制 */
	SharedAggInfo *shared_info; /* 每个工作者一个条目 */
} AggState;

/* ----------------
 *	WindowAggState 信息
 * ----------------
 */
/* 这些结构在 nodeWindowAgg.c 中是私有的： */
typedef struct WindowStatePerFuncData *WindowStatePerFunc;
typedef struct WindowStatePerAggData *WindowStatePerAgg;

/*
 * WindowAggStatus -- 用于跟踪 WindowAggState 的状态
 */
typedef enum WindowAggStatus
{
	WINDOWAGG_DONE,				/* 没有更多的处理要做 */
	WINDOWAGG_RUN,				/* 窗口函数的正常处理 */
	WINDOWAGG_PASSTHROUGH,		/* 不评估窗口函数 */
	WINDOWAGG_PASSTHROUGH_STRICT	/* 通过传递并且不会在暂存期间存储新
									 * 元组 */
} WindowAggStatus;

typedef struct WindowAggState
{
	ScanState	ss;				/* 它的第一个字段是 NodeTag */

	/* 这些字段由 ExecInitExpr 填充： */
	List	   *funcs;			/* 目标列表中的所有 WindowFunc 节点 */
	int			numfuncs;		/* 窗口函数的总数量 */
	int			numaggs;		/* 普通聚合的数量 */

	WindowStatePerFunc perfunc; /* 每个窗口函数的信息 */
	WindowStatePerAgg peragg;	/* 每个普通聚合的信息 */
	ExprState  *partEqfunction; /* 用于分区列的相等函数 */
	ExprState  *ordEqfunction;	/* 用于排序列的相等函数 */
	Tuplestorestate *buffer;	/* 存储当前分区的行 */
	int			current_ptr;	/* 当前行的读取指针# */
	int			framehead_ptr;	/* 如果使用，帧头的读取指针# */
	int			frametail_ptr;	/* 如果使用，帧尾的读取指针# */
	int			grouptail_ptr;	/* 如果使用，组尾的读取指针# */
	int64		spooled_rows;	/* 缓冲区中的总行数 */
	int64		currentpos;		/* 当前行在分区中的位置 */
	int64		frameheadpos;	/* 当前帧头的位置 */
	int64		frametailpos;	/* 当前帧尾位置（帧结束+1） */
	/* 使用结构体指针以避免在此处包含windowapi.h */
	struct WindowObjectData *agg_winobj;	/* 用于聚合获取的winobj */
	int64		aggregatedbase; /* 当前聚合的起始行 */
	int64		aggregatedupto; /* 在这一行之前的行被聚合 */
	WindowAggStatus status;		/* WindowAggState的运行状态 */

	int			frameOptions;	/* frame_clause选项，参见WindowDef */
	ExprState  *startOffset;	/* 起始界限偏移量的表达式 */
	ExprState  *endOffset;		/* 结束界限偏移量的表达式 */
	Datum		startOffsetValue;	/* startOffset评估的结果 */
	Datum		endOffsetValue; /* endOffset评估的结果 */

	/* 这些字段用于RANGE偏移PRECEDING/FOLLOWING： */
	FmgrInfo	startInRangeFunc;	/* startOffset的in_range函数 */
	FmgrInfo	endInRangeFunc; /* endOffset的in_range函数 */
	Oid			inRangeColl;	/* in_range测试的排序规则 */
	bool		inRangeAsc;		/* 使用 ASC 排序顺序进行范围测试？ */
	bool		inRangeNullsFirst;	/* 空值在范围测试中优先排序吗？ */

	/* 这些字段用于 GROUPS 模式： */
	int64		currentgroup;	/* 当前行在分区中的同级组编号 */
	int64		frameheadgroup; /* 框架头行的同级组编号 */
	int64		frametailgroup; /* 框架尾行的同级组编号 */
	int64		groupheadpos;	/* 当前行的同级组头位置 */
	int64		grouptailpos;	/* " " " " 尾位置（组结束+1） */

	MemoryContext partcontext;	/* 分区生命周期数据的上下文 */
	MemoryContext aggcontext;	/* 聚合工作数据的共享上下文 */
	MemoryContext curaggcontext;	/* 当前聚合的工作数据 */
	ExprContext *tmpcontext;	/* 短期评估上下文 */

	ExprState  *runcondition;	/* 必须保持为真的条件，否则
								 * WindowAgg 的执行将结束或
								 * 进入通行模式。NULL 表示没有此类条件。 */

	bool		use_pass_through;	/* 如果为假，当
									 * runcondition 不再为真时停止执行。否则
									 * 只停止评估窗口函数。 */
	bool		top_window;		/* 如果这是最上层的 WindowAgg 或
								 * 查询层中唯一的 WindowAgg 则为真 */
	bool		all_first;		/* 如果扫描开始则为真 */
	bool		partition_spooled;	/* 如果当前分区中的所有元组
									 * 都已被存储到元组存储中则为真 */
	bool		more_partitions;	/* 如果在此分区之后还有更多分区则为真 */
	bool		framehead_valid;	/* 如果当前行的框架头位置已知至
									 * 今则为真 */
	bool		frametail_valid;	/* 如果当前行的框架尾位置已知至
									 * 今则为真 */
	bool		grouptail_valid;	/* 如果当前行的组尾位置已知至
									 * 今则为真 */

	TupleTableSlot *first_part_slot;	/* 当前分区或下一个分区的第一个元组 */
	TupleTableSlot *framehead_slot; /* 当前框架的第一个元组 */
	TupleTableSlot *frametail_slot; /* 当前框架后的第一个元组 */

	/* 从元组存储中回取的元组的临时槽 */
	TupleTableSlot *agg_row_slot;
	TupleTableSlot *temp_slot_1;
	TupleTableSlot *temp_slot_2;
} WindowAggState;

/* ----------------
 *	 UniqueState 信息
 *
 *		唯一节点在排序节点"之上"使用以丢弃
 *		排序阶段返回的重复元组。基本上
 *		它所做的就是将来自子计划的当前元组
 *		与之前获取的元组（存储在其结果槽中）
 *		进行比较。如果这两个元组在所有重要字段上完全相同，则
 *		我们只需从排序中获取另一个元组并重试。
 * ----------------
 */
typedef struct UniqueState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	ExprState  *eqfunction;		/* 元组相等资格 */
} UniqueState;

/* ----------------
 * GatherState 信息
 *
 *		Gather 节点启动 1 个或多个并行工作者，在这些工作者中运行子计划，
 *		并收集结果。
 * ----------------
 */
typedef struct GatherState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	bool		initialized;	/* 工作者启动了吗？ */
	bool		need_to_scan_locally;	/* 需要从本地计划中读取吗？ */
	int64		tuples_needed;	/* 元组边界，见 ExecSetTupleBound */
	/* 这些字段只设置一次: */
	TupleTableSlot *funnel_slot;
	struct ParallelExecutorInfo *pei;
	/* 所有剩余字段在重新扫描期间被重新初始化: */
	int			nworkers_launched;	/* 原始工作线程数量 */
	int			nreaders;		/* 仍然活跃的工作线程数量 */
	int			nextreader;		/* 下一个尝试读取的工作线程 */
	struct TupleQueueReader **reader;	/* 包含 nreaders 活动条目的数组 */
} GatherState;

/* ----------------
 * GatherMergeState 信息
 *
 *		Gather merge 节点启动一个或多个并行工作线程，运行一个
 *		子计划，该子计划在每个工作线程中生成排序后的输出，
 *		然后将结果合并成一个单一的排序流。
 * ----------------
 */
struct GMReaderTupleBuffer;		/* 在 nodeGatherMerge.c 中私有 */

typedef struct GatherMergeState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	bool		initialized;	/* 工作者启动了吗？ */
	bool		gm_initialized; /* gather_merge_init() 完成了吗? */
	bool		need_to_scan_locally;	/* 需要从本地计划中读取吗？ */
	int64		tuples_needed;	/* 元组边界，见 ExecSetTupleBound */
	/* 这些字段只设置一次: */
	TupleDesc	tupDesc;		/* 子计划结果元组的描述符 */
	int			gm_nkeys;		/* 排序列的数量 */
	SortSupport gm_sortkeys;	/* 长度为 gm_nkeys 的数组 */
	struct ParallelExecutorInfo *pei;
	/* 所有剩余字段在重新扫描期间被重新初始化 */
	/* (但数组不会重新分配，只是被清空) */
	int			nworkers_launched;	/* 原始工作线程数量 */
	int			nreaders;		/* 活跃工作线程的数量 */
	TupleTableSlot **gm_slots;	/* 包含 nreaders+1 条目的数组 */
	struct TupleQueueReader **reader;	/* 包含 nreaders 活动条目的数组 */
	struct GMReaderTupleBuffer *gm_tuple_buffers;	/* nreaders 元组缓存 */
	struct binaryheap *gm_heap; /* 插槽索引的二进制堆 */
} GatherMergeState;

/* ----------------
 *	 EXPLAIN ANALYZE 显示的值
 * ----------------
 */
typedef struct HashInstrumentation
{
	int			nbuckets;		/* 执行结束时的桶数量 */
	int			nbuckets_original;	/* 计划的桶数量 */
	int			nbatch;			/* 执行结束时的批次数量 */
	int			nbatch_original;	/* 计划的批次数量 */
	Size		space_peak;		/* 峰值内存使用量（以字节为单位） */
} HashInstrumentation;

/* ----------------
 *	 用于每个工作线程哈希信息的共享内存容器
 * ----------------
 */
typedef struct SharedHashInfo
{
	int			num_workers;
	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
} SharedHashInfo;

/* ----------------
 *	 HashState 信息
 * ----------------
 */
typedef struct HashState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	HashJoinTable hashtable;	/* 哈希连接的哈希表 */
	List	   *hashkeys;		/* ExprState 节点列表 */

	/*
	 * 在并行哈希连接中，领导者保留指向其共享信息字段中共享内存统计区域的指针，
	 * 然后在 DSM 关闭之前将共享内存信息复制回本地存储。共享信息字段在工作线程中保持为 NULL，
	 * 或在非并行连接中。
	 */
	SharedHashInfo *shared_info;

	/*
	 * 如果我们正在收集哈希统计信息，这个指针指向一个最初为零的收集区域，
	 * 该区域可以是本地存储或共享内存；无论如何，这只是针对一个进程。
	 */
	HashInstrumentation *hinstrument;

	/* 并行哈希状态。 */
	struct ParallelHashJoinState *parallel_state;
} HashState;

/* ----------------
 *	 SetOpState 信息
 *
 *		即使在“排序”模式下，SetOp 节点也比简单的 Unique 更复杂，
 *		因为我们必须计算要返回多少个重复项。但我们也支持哈希，
 *		所以这实际上更像是 Agg 的削减形式。
 * ----------------
 */
/* 这个结构在 nodeSetOp.c 中是私有的: */
typedef struct SetOpStatePerGroupData *SetOpStatePerGroup;

typedef struct SetOpState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	ExprState  *eqfunction;		/* 相等比较器 */
	Oid		   *eqfuncoids;		/* 每个分组字段的相等函数 */
	FmgrInfo   *hashfunctions;	/* 每个分组字段的哈希函数 */
	bool		setop_done;		/* 指示输出扫描的完成 */
	long		numOutput;		/* 剩余要输出的重复项数量 */
	/* 这些字段在 SETOP_SORTED 模式下使用: */
	SetOpStatePerGroup pergroup;	/* 每组工作状态 */
	HeapTuple	grp_firstTuple; /* 当前组的第一个元组的副本 */
	/* 这些字段在 SETOP_HASHED 模式下使用: */
	TupleHashTable hashtable;	/* 每个组一个条目的哈希表 */
	MemoryContext tableContext; /* 包含哈希表的内存上下文 */
	bool		table_filled;	/* 哈希表已经填充完毕吗？ */
	TupleHashIterator hashiter; /* 遍历哈希表用 */
} SetOpState;

/* ----------------
 *	 LockRowsState 信息
 *
 *		LockRows 节点用于强制执行 FOR [KEY] UPDATE/SHARE 锁定。
 * ----------------
 */
typedef struct LockRowsState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	List	   *lr_arowMarks;	/* ExecAuxRowMarks 列表 */
	EPQState	lr_epqstate;	/* 用于评估 EvalPlanQual 重新检查 */
} LockRowsState;

/* ----------------
 *	 LimitState 信息
 *
 *		Limit 节点用于强制执行 LIMIT/OFFSET 子句。
 *		它们仅选择其子计划输出的所需子范围。
 *
 * offset 是要跳过的初始元组数量（0 不做任何操作）。
 * count 是在跳过 offset 元组后要返回的元组数量。
 * 如果未指定限制计数，则 count 是未定义的，而 noCount 为 true。
 * 当 lstate == LIMIT_INITIAL 时，offset/count/noCount 尚未设置。
 * ----------------
 */
typedef enum
{
	LIMIT_INITIAL,				/* LIMIT 节点的初始状态 */
	LIMIT_RESCAN,				/* 在重新计算参数后重新扫描 */
	LIMIT_EMPTY,				/* 没有可返回的行 */
	LIMIT_INWINDOW,				/* 在窗口中已返回一行 */
	LIMIT_WINDOWEND_TIES,		/* 已返回一个绑定行 */
	LIMIT_SUBPLANEOF,			/* 到达子计划的结束（在窗口内） */
	LIMIT_WINDOWEND,			/* 超出了窗口的末端 */
	LIMIT_WINDOWSTART			/* 超出了窗口的起始 */
} LimitStateCond;

typedef struct LimitState
{
	PlanState	ps;				/* 它的第一个字段是 NodeTag */
	ExprState  *limitOffset;	/* OFFSET 参数，或者如果没有则为 NULL */
	ExprState  *limitCount;		/* COUNT 参数，或者如果没有则为 NULL */
	LimitOption limitOption;	/* 限制规范类型 */
	int64		offset;			/* 当前 OFFSET 值 */
	int64		count;			/* 当前 COUNT，如果有的话 */
	bool		noCount;		/* 如果为真，则忽略计数 */
	LimitStateCond lstate;		/* 状态机状态，如上所述 */
	int64		position;		/* 返回的最后一个元组的基于 1 的索引 */
	TupleTableSlot *subSlot;	/* 从子计划中最后获得的元组 */
	ExprState  *eqfunction;		/* 在 WITH TIES 选项情况下的元组相等条件 */
	TupleTableSlot *last_slot;	/* 用于评估绑定的槽 */
} LimitState;

#endif							/* EXECNODES_H */
